Merge pull request #788 from luoliwoshang/llcppsymg/classname
llcppsymg:refine class fetch,test case,-v
This commit is contained in:
119
chore/_xtool/llcppsymg/_cmptest/clangutils_test/clangutils.go
Normal file
119
chore/_xtool/llcppsymg/_cmptest/clangutils_test/clangutils.go
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/goplus/llgo/c"
|
||||||
|
"github.com/goplus/llgo/c/clang"
|
||||||
|
"github.com/goplus/llgo/chore/_xtool/llcppsymg/clangutils"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
TestClangUtil()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClangUtil() {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
content string
|
||||||
|
isTemp bool
|
||||||
|
isCpp bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "C Header File",
|
||||||
|
content: `
|
||||||
|
int test_function(int a, int b);
|
||||||
|
void another_function(void);
|
||||||
|
`,
|
||||||
|
isTemp: false,
|
||||||
|
isCpp: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "C++ Temp File",
|
||||||
|
content: `
|
||||||
|
class TestClass {
|
||||||
|
public:
|
||||||
|
void test_method();
|
||||||
|
static int static_method(float f);
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace TestNamespace {
|
||||||
|
void namespaced_function();
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
isTemp: true,
|
||||||
|
isCpp: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
fmt.Printf("=== Test Case: %s ===\n", tc.name)
|
||||||
|
|
||||||
|
var filePath string
|
||||||
|
var tempFile *os.File
|
||||||
|
if tc.isTemp {
|
||||||
|
filePath = tc.content
|
||||||
|
} else {
|
||||||
|
var err error
|
||||||
|
tempFile, err = os.CreateTemp("", "test_*.h")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Failed to create temporary file: %v\n", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = tempFile.Write([]byte(tc.content))
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Failed to write to temporary file: %v\n", err)
|
||||||
|
tempFile.Close()
|
||||||
|
os.Remove(tempFile.Name())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
tempFile.Close()
|
||||||
|
filePath = tempFile.Name()
|
||||||
|
}
|
||||||
|
|
||||||
|
config := &clangutils.Config{
|
||||||
|
File: filePath,
|
||||||
|
Temp: tc.isTemp,
|
||||||
|
IsCpp: tc.isCpp,
|
||||||
|
}
|
||||||
|
index, unit, err := clangutils.CreateTranslationUnit(config)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("CreateTranslationUnit failed: %v\n", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("CreateTranslationUnit succeeded")
|
||||||
|
|
||||||
|
cursor := unit.Cursor()
|
||||||
|
|
||||||
|
clangutils.VisitChildren(cursor, func(cursor, parent clang.Cursor) clang.ChildVisitResult {
|
||||||
|
switch cursor.Kind {
|
||||||
|
case clang.CursorFunctionDecl, clang.CursorCXXMethod:
|
||||||
|
funcName := cursor.String()
|
||||||
|
fmt.Printf("Function/Method: %s\n", c.GoString(funcName.CStr()))
|
||||||
|
parts := clangutils.BuildScopingParts(cursor)
|
||||||
|
fmt.Printf("Scoping parts: %v\n", parts)
|
||||||
|
funcName.Dispose()
|
||||||
|
case clang.CursorClassDecl:
|
||||||
|
className := cursor.String()
|
||||||
|
fmt.Printf("Class: %s\n", c.GoString(className.CStr()))
|
||||||
|
className.Dispose()
|
||||||
|
case clang.CursorNamespace:
|
||||||
|
namespaceName := cursor.String()
|
||||||
|
fmt.Printf("Namespace: %s\n", c.GoString(namespaceName.CStr()))
|
||||||
|
namespaceName.Dispose()
|
||||||
|
}
|
||||||
|
return clang.ChildVisit_Recurse
|
||||||
|
})
|
||||||
|
index.Dispose()
|
||||||
|
unit.Dispose()
|
||||||
|
|
||||||
|
if !tc.isTemp && tempFile != nil {
|
||||||
|
os.Remove(tempFile.Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println()
|
||||||
|
}
|
||||||
|
}
|
||||||
23
chore/_xtool/llcppsymg/_cmptest/clangutils_test/llgo.expect
Normal file
23
chore/_xtool/llcppsymg/_cmptest/clangutils_test/llgo.expect
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
#stdout
|
||||||
|
=== Test Case: C Header File ===
|
||||||
|
CreateTranslationUnit succeeded
|
||||||
|
Function/Method: test_function
|
||||||
|
Scoping parts: [test_function]
|
||||||
|
Function/Method: another_function
|
||||||
|
Scoping parts: [another_function]
|
||||||
|
|
||||||
|
=== Test Case: C++ Temp File ===
|
||||||
|
CreateTranslationUnit succeeded
|
||||||
|
Class: TestClass
|
||||||
|
Function/Method: test_method
|
||||||
|
Scoping parts: [TestClass test_method]
|
||||||
|
Function/Method: static_method
|
||||||
|
Scoping parts: [TestClass static_method]
|
||||||
|
Namespace: TestNamespace
|
||||||
|
Function/Method: namespaced_function
|
||||||
|
Scoping parts: [TestNamespace namespaced_function]
|
||||||
|
|
||||||
|
|
||||||
|
#stderr
|
||||||
|
|
||||||
|
#exit 0
|
||||||
332
chore/_xtool/llcppsymg/_cmptest/config_test/config.go
Normal file
332
chore/_xtool/llcppsymg/_cmptest/config_test/config.go
Normal file
@@ -0,0 +1,332 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/goplus/llgo/chore/_xtool/llcppsymg/config"
|
||||||
|
"github.com/goplus/llgo/chore/_xtool/llcppsymg/config/cfgparse"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
TestGetConf()
|
||||||
|
TestParseLibs()
|
||||||
|
TestGenDylibPaths()
|
||||||
|
TestParseCFlags()
|
||||||
|
TestGenHeaderFilePath()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetConf() {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
input string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "SQLite configuration",
|
||||||
|
input: `{
|
||||||
|
"name": "sqlite",
|
||||||
|
"cflags": "-I/opt/homebrew/opt/sqlite/include",
|
||||||
|
"include": ["sqlite3.h"],
|
||||||
|
"libs": "-L/opt/homebrew/opt/sqlite/lib -lsqlite3",
|
||||||
|
"trimPrefixes": ["sqlite3_"],
|
||||||
|
"cplusplus": false
|
||||||
|
}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Lua configuration",
|
||||||
|
input: `{
|
||||||
|
"name": "lua",
|
||||||
|
"cflags": "-I/opt/homebrew/include/lua",
|
||||||
|
"include": ["lua.h"],
|
||||||
|
"libs": "-L/opt/homebrew/lib -llua -lm",
|
||||||
|
"trimPrefixes": ["lua_", "lua_"],
|
||||||
|
"cplusplus": false
|
||||||
|
}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Invalid JSON",
|
||||||
|
input: `{invalid json}`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
fmt.Printf("=== Test case: %s ===\n", tc.name)
|
||||||
|
result, err := config.GetConf([]byte(tc.input))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Error:", err.Error())
|
||||||
|
} else {
|
||||||
|
fmt.Println("Name:", result.Config.Name)
|
||||||
|
fmt.Println("CFlags:", result.Config.CFlags)
|
||||||
|
fmt.Println("Libs:", result.Config.Libs)
|
||||||
|
fmt.Println("Include:", strings.Join(result.Config.Include, ", "))
|
||||||
|
fmt.Println("TrimPrefixes:", strings.Join(result.Config.TrimPrefixes, ", "))
|
||||||
|
fmt.Println("Cplusplus:", result.Config.Cplusplus)
|
||||||
|
}
|
||||||
|
fmt.Println()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseLibs() {
|
||||||
|
fmt.Println("=== Test ParseLibs ===")
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
input string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Lua library",
|
||||||
|
input: "-L/opt/homebrew/lib -llua -lm",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "SQLite library",
|
||||||
|
input: "-L/opt/homebrew/opt/sqlite/lib -lsqlite3",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "INIReader library",
|
||||||
|
input: "-L/opt/homebrew/Cellar/inih/58/lib -lINIReader",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Multiple library paths",
|
||||||
|
input: "-L/opt/homebrew/lib -L/usr/lib -llua",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "No valid library",
|
||||||
|
input: "-L/opt/homebrew/lib",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
fmt.Printf("Test case: %s\n", tc.name)
|
||||||
|
fmt.Printf("Input: %s\n", tc.input)
|
||||||
|
|
||||||
|
conf := cfgparse.ParseLibs(tc.input)
|
||||||
|
|
||||||
|
fmt.Println("Paths:", conf.Paths)
|
||||||
|
fmt.Println("Names:", conf.Names)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenDylibPaths() {
|
||||||
|
fmt.Println("=== Test GenDylibPaths ===")
|
||||||
|
|
||||||
|
tempDir := os.TempDir()
|
||||||
|
tempDefaultPath := filepath.Join(tempDir, "symblib")
|
||||||
|
affix := ".dylib"
|
||||||
|
if runtime.GOOS == "linux" {
|
||||||
|
affix = ".so"
|
||||||
|
}
|
||||||
|
err := os.MkdirAll(tempDefaultPath, 0755)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Failed to create temp default path: %v\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
dylib1 := filepath.Join(tempDir, "libsymb1"+affix)
|
||||||
|
dylib2 := filepath.Join(tempDir, "libsymb2"+affix)
|
||||||
|
defaultDylib3 := filepath.Join(tempDefaultPath, "libsymb3"+affix)
|
||||||
|
|
||||||
|
os.Create(dylib1)
|
||||||
|
os.Create(dylib2)
|
||||||
|
os.Create(defaultDylib3)
|
||||||
|
defer os.Remove(dylib1)
|
||||||
|
defer os.Remove(dylib2)
|
||||||
|
defer os.Remove(defaultDylib3)
|
||||||
|
defer os.Remove(tempDefaultPath)
|
||||||
|
|
||||||
|
testCase := []struct {
|
||||||
|
name string
|
||||||
|
conf *cfgparse.Libs
|
||||||
|
defaultPaths []string
|
||||||
|
want []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "existing dylib",
|
||||||
|
conf: &cfgparse.Libs{
|
||||||
|
Names: []string{"symb1"},
|
||||||
|
Paths: []string{tempDir},
|
||||||
|
},
|
||||||
|
defaultPaths: []string{},
|
||||||
|
want: []string{dylib1},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "existing dylibs",
|
||||||
|
conf: &cfgparse.Libs{
|
||||||
|
Names: []string{"symb1", "symb2"},
|
||||||
|
Paths: []string{tempDir},
|
||||||
|
},
|
||||||
|
defaultPaths: []string{},
|
||||||
|
want: []string{dylib1, dylib2},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "existint default paths",
|
||||||
|
conf: &cfgparse.Libs{
|
||||||
|
Names: []string{"symb1", "symb3"},
|
||||||
|
Paths: []string{tempDir},
|
||||||
|
},
|
||||||
|
defaultPaths: []string{tempDefaultPath},
|
||||||
|
want: []string{dylib1, defaultDylib3},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "existint default paths & not found",
|
||||||
|
conf: &cfgparse.Libs{
|
||||||
|
Names: []string{"symb1", "symb3", "math"},
|
||||||
|
Paths: []string{tempDir},
|
||||||
|
},
|
||||||
|
defaultPaths: []string{tempDefaultPath},
|
||||||
|
want: []string{dylib1, defaultDylib3},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no existing dylib",
|
||||||
|
conf: &cfgparse.Libs{
|
||||||
|
Names: []string{"notexist"},
|
||||||
|
Paths: []string{tempDir},
|
||||||
|
},
|
||||||
|
want: []string{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range testCase {
|
||||||
|
fmt.Printf("Test case: %s\n", tc.name)
|
||||||
|
paths, notFounds, err := tc.conf.GenDylibPaths(tc.defaultPaths)
|
||||||
|
|
||||||
|
if len(notFounds) > 0 {
|
||||||
|
fmt.Println("notFounds", notFounds)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error: %v\n", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, path := range paths {
|
||||||
|
found := false
|
||||||
|
for _, wantPath := range tc.want {
|
||||||
|
if path == wantPath {
|
||||||
|
found = true
|
||||||
|
fileName := filepath.Base(path)
|
||||||
|
if runtime.GOOS == "linux" {
|
||||||
|
fileName = strings.TrimSuffix(fileName, ".so")
|
||||||
|
} else {
|
||||||
|
fileName = strings.TrimSuffix(fileName, ".dylib")
|
||||||
|
}
|
||||||
|
fmt.Printf("Path %s is in the expected paths\n", fileName)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
fmt.Printf("Path %s is not in the expected paths\n", path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseCFlags() {
|
||||||
|
fmt.Println("=== Test ParseCFlags ===")
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
input string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Single include path",
|
||||||
|
input: "-I/usr/include",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Multiple include paths",
|
||||||
|
input: "-I/usr/include -I/opt/homebrew/include",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Include paths mixed with other flags",
|
||||||
|
input: "-I/usr/include -DDEBUG -I/opt/local/include -Wall",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Empty input",
|
||||||
|
input: "",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
fmt.Printf("Test case: %s\n", tc.name)
|
||||||
|
fmt.Printf("Input: %s\n", tc.input)
|
||||||
|
|
||||||
|
conf := cfgparse.ParseCFlags(tc.input)
|
||||||
|
|
||||||
|
fmt.Println("Paths:", conf.Paths)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenHeaderFilePath() {
|
||||||
|
fmt.Println("=== Test GenHeaderFilePath ===")
|
||||||
|
|
||||||
|
tempDir := os.TempDir()
|
||||||
|
temDir2 := filepath.Join(tempDir, "include")
|
||||||
|
tempFile1 := filepath.Join(tempDir, "test1.h")
|
||||||
|
tempFile2 := filepath.Join(tempDir, "test2.h")
|
||||||
|
tempFile3 := filepath.Join(temDir2, "test3.h")
|
||||||
|
os.MkdirAll(temDir2, 0755)
|
||||||
|
os.Create(tempFile1)
|
||||||
|
os.Create(tempFile2)
|
||||||
|
os.Create(tempFile3)
|
||||||
|
defer os.Remove(tempFile1)
|
||||||
|
defer os.Remove(tempFile2)
|
||||||
|
defer os.Remove(tempFile3)
|
||||||
|
defer os.Remove(temDir2)
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
cflags string
|
||||||
|
files []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Valid files",
|
||||||
|
cflags: "-I" + tempDir,
|
||||||
|
files: []string{"test1.h", "test2.h"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Mixed existing and non-existing files",
|
||||||
|
cflags: "-I" + tempDir,
|
||||||
|
files: []string{"test1.h", "nonexistent.h"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Multiple include paths",
|
||||||
|
cflags: "-I" + tempDir + " -I" + temDir2,
|
||||||
|
files: []string{"test1.h", "test2.h", "test3.h"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "No existing files",
|
||||||
|
cflags: "-I" + tempDir,
|
||||||
|
files: []string{"nonexistent1.h", "nonexistent2.h"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Empty file list",
|
||||||
|
cflags: "-I/usr/include",
|
||||||
|
files: []string{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
fmt.Printf("Test case: %s\n", tc.name)
|
||||||
|
fmt.Printf("Input files: %v\n", tc.files)
|
||||||
|
|
||||||
|
cflag := cfgparse.ParseCFlags(tc.cflags)
|
||||||
|
result, notFounds, err := cflag.GenHeaderFilePaths(tc.files)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error: %v\n", err)
|
||||||
|
}
|
||||||
|
if len(notFounds) > 0 {
|
||||||
|
fmt.Println("notFounds", notFounds)
|
||||||
|
}
|
||||||
|
if result != nil {
|
||||||
|
relativeResult := make([]string, len(result))
|
||||||
|
for i, path := range result {
|
||||||
|
relativeResult[i] = filepath.Base(path)
|
||||||
|
}
|
||||||
|
fmt.Printf("Output: %v\n", relativeResult)
|
||||||
|
}
|
||||||
|
fmt.Println()
|
||||||
|
}
|
||||||
|
}
|
||||||
97
chore/_xtool/llcppsymg/_cmptest/config_test/llgo.expect
Normal file
97
chore/_xtool/llcppsymg/_cmptest/config_test/llgo.expect
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
#stdout
|
||||||
|
=== Test case: SQLite configuration ===
|
||||||
|
Name: sqlite
|
||||||
|
CFlags: -I/opt/homebrew/opt/sqlite/include
|
||||||
|
Libs: -L/opt/homebrew/opt/sqlite/lib -lsqlite3
|
||||||
|
Include: sqlite3.h
|
||||||
|
TrimPrefixes: sqlite3_
|
||||||
|
Cplusplus: false
|
||||||
|
|
||||||
|
=== Test case: Lua configuration ===
|
||||||
|
Name: lua
|
||||||
|
CFlags: -I/opt/homebrew/include/lua
|
||||||
|
Libs: -L/opt/homebrew/lib -llua -lm
|
||||||
|
Include: lua.h
|
||||||
|
TrimPrefixes: lua_, lua_
|
||||||
|
Cplusplus: false
|
||||||
|
|
||||||
|
=== Test case: Invalid JSON ===
|
||||||
|
Error: failed to parse config
|
||||||
|
|
||||||
|
=== Test ParseLibs ===
|
||||||
|
Test case: Lua library
|
||||||
|
Input: -L/opt/homebrew/lib -llua -lm
|
||||||
|
Paths: [/opt/homebrew/lib]
|
||||||
|
Names: [lua m]
|
||||||
|
Test case: SQLite library
|
||||||
|
Input: -L/opt/homebrew/opt/sqlite/lib -lsqlite3
|
||||||
|
Paths: [/opt/homebrew/opt/sqlite/lib]
|
||||||
|
Names: [sqlite3]
|
||||||
|
Test case: INIReader library
|
||||||
|
Input: -L/opt/homebrew/Cellar/inih/58/lib -lINIReader
|
||||||
|
Paths: [/opt/homebrew/Cellar/inih/58/lib]
|
||||||
|
Names: [INIReader]
|
||||||
|
Test case: Multiple library paths
|
||||||
|
Input: -L/opt/homebrew/lib -L/usr/lib -llua
|
||||||
|
Paths: [/opt/homebrew/lib /usr/lib]
|
||||||
|
Names: [lua]
|
||||||
|
Test case: No valid library
|
||||||
|
Input: -L/opt/homebrew/lib
|
||||||
|
Paths: [/opt/homebrew/lib]
|
||||||
|
Names: []
|
||||||
|
=== Test GenDylibPaths ===
|
||||||
|
Test case: existing dylib
|
||||||
|
Path libsymb1 is in the expected paths
|
||||||
|
Test case: existing dylibs
|
||||||
|
Path libsymb1 is in the expected paths
|
||||||
|
Path libsymb2 is in the expected paths
|
||||||
|
Test case: existint default paths
|
||||||
|
Path libsymb1 is in the expected paths
|
||||||
|
Path libsymb3 is in the expected paths
|
||||||
|
Test case: existint default paths & not found
|
||||||
|
notFounds [math]
|
||||||
|
Path libsymb1 is in the expected paths
|
||||||
|
Path libsymb3 is in the expected paths
|
||||||
|
Test case: no existing dylib
|
||||||
|
notFounds [notexist]
|
||||||
|
Error: failed to find any libraries
|
||||||
|
=== Test ParseCFlags ===
|
||||||
|
Test case: Single include path
|
||||||
|
Input: -I/usr/include
|
||||||
|
Paths: [/usr/include]
|
||||||
|
Test case: Multiple include paths
|
||||||
|
Input: -I/usr/include -I/opt/homebrew/include
|
||||||
|
Paths: [/usr/include /opt/homebrew/include]
|
||||||
|
Test case: Include paths mixed with other flags
|
||||||
|
Input: -I/usr/include -DDEBUG -I/opt/local/include -Wall
|
||||||
|
Paths: [/usr/include /opt/local/include]
|
||||||
|
Test case: Empty input
|
||||||
|
Input:
|
||||||
|
Paths: []
|
||||||
|
=== Test GenHeaderFilePath ===
|
||||||
|
Test case: Valid files
|
||||||
|
Input files: [test1.h test2.h]
|
||||||
|
Output: [test1.h test2.h]
|
||||||
|
|
||||||
|
Test case: Mixed existing and non-existing files
|
||||||
|
Input files: [test1.h nonexistent.h]
|
||||||
|
notFounds [nonexistent.h]
|
||||||
|
Output: [test1.h]
|
||||||
|
|
||||||
|
Test case: Multiple include paths
|
||||||
|
Input files: [test1.h test2.h test3.h]
|
||||||
|
Output: [test1.h test2.h test3.h]
|
||||||
|
|
||||||
|
Test case: No existing files
|
||||||
|
Input files: [nonexistent1.h nonexistent2.h]
|
||||||
|
Error: failed to find any header files
|
||||||
|
notFounds [nonexistent1.h nonexistent2.h]
|
||||||
|
|
||||||
|
Test case: Empty file list
|
||||||
|
Input files: []
|
||||||
|
Error: failed to find any header files
|
||||||
|
|
||||||
|
|
||||||
|
#stderr
|
||||||
|
|
||||||
|
#exit 0
|
||||||
44
chore/_xtool/llcppsymg/_cmptest/parse_test/llgo.expect
Normal file
44
chore/_xtool/llcppsymg/_cmptest/parse_test/llgo.expect
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
#stdout
|
||||||
|
=== Test NewSymbolProcessor ===
|
||||||
|
Before: No prefixes After: Prefixes: [lua_ luaL_]
|
||||||
|
|
||||||
|
=== Test RemovePrefix ===
|
||||||
|
Before: lua_closethread After: closethread
|
||||||
|
Before: luaL_checknumber After: checknumber
|
||||||
|
|
||||||
|
=== Test ToGoName ===
|
||||||
|
Before: lua_closethread After: Closethread
|
||||||
|
Before: luaL_checknumber After: Checknumber
|
||||||
|
Before: sqlite3_close_v2 After: CloseV2
|
||||||
|
Before: sqlite3_callback After: Callback
|
||||||
|
Before: GetReal After: GetReal
|
||||||
|
Before: GetBoolean After: GetBoolean
|
||||||
|
Before: INIReader After: Reader
|
||||||
|
|
||||||
|
=== Test GenMethodName ===
|
||||||
|
Before: Class: INIReader, Name: INIReader After: (*INIReader).Init
|
||||||
|
Before: Class: INIReader, Name: INIReader After: (*INIReader).Dispose
|
||||||
|
Before: Class: INIReader, Name: HasValue After: (*INIReader).HasValue
|
||||||
|
|
||||||
|
=== Test AddSuffix ===
|
||||||
|
Before: Class: INIReader, Method: INIReader After: (*Reader).Init
|
||||||
|
Before: Class: INIReader, Method: INIReader After: (*Reader).Init__1
|
||||||
|
Before: Class: INIReader, Method: ParseError After: (*Reader).ParseError
|
||||||
|
Before: Class: INIReader, Method: HasValue After: (*Reader).HasValue
|
||||||
|
|
||||||
|
=== Test Case: C++ Class with Methods ===
|
||||||
|
Parsed Symbols:
|
||||||
|
Symbol Map GoName: (*Reader).Init__1, ProtoName In HeaderFile: INIReader::INIReader(const char *, int), MangledName: _ZN9INIReaderC1EPKci
|
||||||
|
Symbol Map GoName: (*Reader).Init, ProtoName In HeaderFile: INIReader::INIReader(const int &), MangledName: _ZN9INIReaderC1ERKi
|
||||||
|
Symbol Map GoName: (*Reader).Dispose, ProtoName In HeaderFile: INIReader::~INIReader(), MangledName: _ZN9INIReaderD1Ev
|
||||||
|
Symbol Map GoName: (*Reader).ParseError, ProtoName In HeaderFile: INIReader::ParseError(), MangledName: _ZNK9INIReader10ParseErrorEv
|
||||||
|
|
||||||
|
=== Test Case: C Functions ===
|
||||||
|
Parsed Symbols:
|
||||||
|
Symbol Map GoName: Compare, ProtoName In HeaderFile: lua_compare(lua_State *, int, int, int), MangledName: lua_compare
|
||||||
|
Symbol Map GoName: Rawequal, ProtoName In HeaderFile: lua_rawequal(lua_State *, int, int), MangledName: lua_rawequal
|
||||||
|
|
||||||
|
|
||||||
|
#stderr
|
||||||
|
|
||||||
|
#exit 0
|
||||||
165
chore/_xtool/llcppsymg/_cmptest/parse_test/parse.go
Normal file
165
chore/_xtool/llcppsymg/_cmptest/parse_test/parse.go
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
"github.com/goplus/llgo/chore/_xtool/llcppsymg/parse"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
TestNewSymbolProcessor()
|
||||||
|
TestRemovePrefix()
|
||||||
|
TestToGoName()
|
||||||
|
TestGenMethodName()
|
||||||
|
TestAddSuffix()
|
||||||
|
TestParseHeaderFile()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewSymbolProcessor() {
|
||||||
|
fmt.Println("=== Test NewSymbolProcessor ===")
|
||||||
|
process := parse.NewSymbolProcessor([]string{"lua_", "luaL_"})
|
||||||
|
fmt.Printf("Before: No prefixes After: Prefixes: %v\n", process.Prefixes)
|
||||||
|
fmt.Println()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRemovePrefix() {
|
||||||
|
fmt.Println("=== Test RemovePrefix ===")
|
||||||
|
process := parse.NewSymbolProcessor([]string{"lua_", "luaL_"})
|
||||||
|
|
||||||
|
testCases := []string{"lua_closethread", "luaL_checknumber"}
|
||||||
|
|
||||||
|
for _, input := range testCases {
|
||||||
|
result := process.TrimPrefixes(input)
|
||||||
|
fmt.Printf("Before: %s After: %s\n", input, result)
|
||||||
|
}
|
||||||
|
fmt.Println()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestToGoName() {
|
||||||
|
fmt.Println("=== Test ToGoName ===")
|
||||||
|
process1 := parse.NewSymbolProcessor([]string{"lua_", "luaL_"})
|
||||||
|
process2 := parse.NewSymbolProcessor([]string{"sqlite3_", "sqlite3_"})
|
||||||
|
process3 := parse.NewSymbolProcessor([]string{"INI"})
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
processor *parse.SymbolProcessor
|
||||||
|
input string
|
||||||
|
}{
|
||||||
|
{process1, "lua_closethread"},
|
||||||
|
{process1, "luaL_checknumber"},
|
||||||
|
{process2, "sqlite3_close_v2"},
|
||||||
|
{process2, "sqlite3_callback"},
|
||||||
|
{process3, "GetReal"},
|
||||||
|
{process3, "GetBoolean"},
|
||||||
|
{process3, "INIReader"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
result := tc.processor.ToGoName(tc.input)
|
||||||
|
fmt.Printf("Before: %s After: %s\n", tc.input, result)
|
||||||
|
}
|
||||||
|
fmt.Println()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenMethodName() {
|
||||||
|
fmt.Println("=== Test GenMethodName ===")
|
||||||
|
process := &parse.SymbolProcessor{}
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
class string
|
||||||
|
name string
|
||||||
|
isDestructor bool
|
||||||
|
}{
|
||||||
|
{"INIReader", "INIReader", false},
|
||||||
|
{"INIReader", "INIReader", true},
|
||||||
|
{"INIReader", "HasValue", false},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
input := fmt.Sprintf("Class: %s, Name: %s", tc.class, tc.name)
|
||||||
|
result := process.GenMethodName(tc.class, tc.name, tc.isDestructor)
|
||||||
|
fmt.Printf("Before: %s After: %s\n", input, result)
|
||||||
|
}
|
||||||
|
fmt.Println()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAddSuffix() {
|
||||||
|
fmt.Println("=== Test AddSuffix ===")
|
||||||
|
process := parse.NewSymbolProcessor([]string{"INI"})
|
||||||
|
methods := []string{
|
||||||
|
"INIReader",
|
||||||
|
"INIReader",
|
||||||
|
"ParseError",
|
||||||
|
"HasValue",
|
||||||
|
}
|
||||||
|
for _, method := range methods {
|
||||||
|
goName := process.ToGoName(method)
|
||||||
|
className := process.ToGoName("INIReader")
|
||||||
|
methodName := process.GenMethodName(className, goName, false)
|
||||||
|
finalName := process.AddSuffix(methodName)
|
||||||
|
input := fmt.Sprintf("Class: INIReader, Method: %s", method)
|
||||||
|
fmt.Printf("Before: %s After: %s\n", input, finalName)
|
||||||
|
}
|
||||||
|
fmt.Println()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseHeaderFile() {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
content string
|
||||||
|
isCpp bool
|
||||||
|
prefixes []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "C++ Class with Methods",
|
||||||
|
content: `
|
||||||
|
class INIReader {
|
||||||
|
public:
|
||||||
|
INIReader(const std::string &filename);
|
||||||
|
INIReader(const char *buffer, size_t buffer_size);
|
||||||
|
~INIReader();
|
||||||
|
int ParseError() const;
|
||||||
|
private:
|
||||||
|
static std::string MakeKey(const std::string §ion, const std::string &name);
|
||||||
|
};
|
||||||
|
`,
|
||||||
|
isCpp: true,
|
||||||
|
prefixes: []string{"INI"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "C Functions",
|
||||||
|
content: `
|
||||||
|
typedef struct lua_State lua_State;
|
||||||
|
int(lua_rawequal)(lua_State *L, int idx1, int idx2);
|
||||||
|
int(lua_compare)(lua_State *L, int idx1, int idx2, int op);
|
||||||
|
`,
|
||||||
|
isCpp: false,
|
||||||
|
prefixes: []string{"lua_"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
fmt.Printf("=== Test Case: %s ===\n", tc.name)
|
||||||
|
|
||||||
|
symbolMap, err := parse.ParseHeaderFile([]string{tc.content}, tc.prefixes, tc.isCpp, true)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error: %v\n", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("Parsed Symbols:")
|
||||||
|
|
||||||
|
var keys []string
|
||||||
|
for key := range symbolMap {
|
||||||
|
keys = append(keys, key)
|
||||||
|
}
|
||||||
|
sort.Strings(keys)
|
||||||
|
|
||||||
|
for _, key := range keys {
|
||||||
|
info := symbolMap[key]
|
||||||
|
fmt.Printf("Symbol Map GoName: %s, ProtoName In HeaderFile: %s, MangledName: %s\n", info.GoName, info.ProtoName, key)
|
||||||
|
}
|
||||||
|
fmt.Println()
|
||||||
|
}
|
||||||
|
}
|
||||||
46
chore/_xtool/llcppsymg/_cmptest/symbol_test/llgo.expect
Normal file
46
chore/_xtool/llcppsymg/_cmptest/symbol_test/llgo.expect
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
#stdout
|
||||||
|
=== Test GetCommonSymbols ===
|
||||||
|
|
||||||
|
Test Case: Lua symbols
|
||||||
|
Common Symbols (4):
|
||||||
|
Mangle: lua_absindex, CPP: lua_absindex(lua_State *, int), Go: Absindex
|
||||||
|
Mangle: lua_arith, CPP: lua_arith(lua_State *, int), Go: Arith
|
||||||
|
Mangle: lua_atpanic, CPP: lua_atpanic(lua_State *, lua_CFunction), Go: Atpanic
|
||||||
|
Mangle: lua_callk, CPP: lua_callk(lua_State *, int, int, lua_KContext, lua_KFunction), Go: Callk
|
||||||
|
|
||||||
|
Test Case: INIReader and Std library symbols
|
||||||
|
Common Symbols (3):
|
||||||
|
Mangle: _ZNK9INIReader12GetInteger64ERKNSt3__112basic_stringIcNS0_11char_traitsIcEENS0_9allocatorIcEEEES8_x, CPP: INIReader::GetInteger64(const std::string &, const std::string &, int64_t), Go: (*Reader).GetInteger64
|
||||||
|
Mangle: _ZNK9INIReader7GetRealERKNSt3__112basic_stringIcNS0_11char_traitsIcEENS0_9allocatorIcEEEES8_d, CPP: INIReader::GetReal(const std::string &, const std::string &, double), Go: (*Reader).GetReal
|
||||||
|
Mangle: _ZNK9INIReader10ParseErrorEv, CPP: INIReader::ParseError(), Go: (*Reader).ParseError
|
||||||
|
|
||||||
|
=== Test ReadExistingSymbolTable ===
|
||||||
|
Symbols read from the file:
|
||||||
|
Symbol Map GoName: (*Reader).Init__1, ProtoName In HeaderFile: INIReader::INIReader(const char *, size_t), MangledName: _ZN9INIReaderC1EPKcm
|
||||||
|
Symbol Map GoName: (*Reader).GetBoolean, ProtoName In HeaderFile: INIReader::GetBoolean(const std::string &, const std::string &, bool), MangledName: _ZNK9INIReader10GetBooleanERKNSt3__112basic_stringIcNS0_11char_traitsIcEENS0_9allocatorIcEEEES8_b
|
||||||
|
Symbol Map GoName: (*Reader).ParseError, ProtoName In HeaderFile: INIReader::ParseError(), MangledName: _ZNK9INIReader10ParseErrorEv
|
||||||
|
Havent existed symb file
|
||||||
|
|
||||||
|
=== Test GenSymbolTableData ===
|
||||||
|
[{
|
||||||
|
"mangle": "lua_absindex",
|
||||||
|
"c++": "lua_absindex(lua_State *, int)",
|
||||||
|
"go": "Absindex"
|
||||||
|
}, {
|
||||||
|
"mangle": "lua_arith",
|
||||||
|
"c++": "lua_arith(lua_State *, int)",
|
||||||
|
"go": "Arith"
|
||||||
|
}, {
|
||||||
|
"mangle": "lua_atpanic",
|
||||||
|
"c++": "lua_atpanic(lua_State *, lua_CFunction)",
|
||||||
|
"go": "Atpanic"
|
||||||
|
}, {
|
||||||
|
"mangle": "lua_callk",
|
||||||
|
"c++": "lua_callk(lua_State *, int, int, lua_KContext, lua_KFunction)",
|
||||||
|
"go": "ModifiedCallk"
|
||||||
|
}]
|
||||||
|
|
||||||
|
|
||||||
|
#stderr
|
||||||
|
|
||||||
|
#exit 0
|
||||||
152
chore/_xtool/llcppsymg/_cmptest/symbol_test/symbol.go
Normal file
152
chore/_xtool/llcppsymg/_cmptest/symbol_test/symbol.go
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
"github.com/goplus/llgo/chore/_xtool/llcppsymg/parse"
|
||||||
|
"github.com/goplus/llgo/chore/_xtool/llcppsymg/symbol"
|
||||||
|
"github.com/goplus/llgo/chore/llcppg/types"
|
||||||
|
"github.com/goplus/llgo/xtool/nm"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
TestGetCommonSymbols()
|
||||||
|
TestReadExistingSymbolTable()
|
||||||
|
TestGenSymbolTableData()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetCommonSymbols() {
|
||||||
|
fmt.Println("=== Test GetCommonSymbols ===")
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
dylibSymbols []*nm.Symbol
|
||||||
|
headerSymbols map[string]*parse.SymbolInfo
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Lua symbols",
|
||||||
|
dylibSymbols: []*nm.Symbol{
|
||||||
|
{Name: symbol.AddSymbolPrefixUnder("lua_absindex", false)},
|
||||||
|
{Name: symbol.AddSymbolPrefixUnder("lua_arith", false)},
|
||||||
|
{Name: symbol.AddSymbolPrefixUnder("lua_atpanic", false)},
|
||||||
|
{Name: symbol.AddSymbolPrefixUnder("lua_callk", false)},
|
||||||
|
{Name: symbol.AddSymbolPrefixUnder("lua_lib_nonexistent", false)},
|
||||||
|
},
|
||||||
|
headerSymbols: map[string]*parse.SymbolInfo{
|
||||||
|
"lua_absindex": {ProtoName: "lua_absindex(lua_State *, int)", GoName: "Absindex"},
|
||||||
|
"lua_arith": {ProtoName: "lua_arith(lua_State *, int)", GoName: "Arith"},
|
||||||
|
"lua_atpanic": {ProtoName: "lua_atpanic(lua_State *, lua_CFunction)", GoName: "Atpanic"},
|
||||||
|
"lua_callk": {ProtoName: "lua_callk(lua_State *, int, int, lua_KContext, lua_KFunction)", GoName: "Callk"},
|
||||||
|
"lua_header_nonexistent": {ProtoName: "lua_header_nonexistent()", GoName: "HeaderNonexistent"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "INIReader and Std library symbols",
|
||||||
|
dylibSymbols: []*nm.Symbol{
|
||||||
|
{Name: symbol.AddSymbolPrefixUnder("ZNK9INIReader12GetInteger64ERKNSt3__112basic_stringIcNS0_11char_traitsIcEENS0_9allocatorIcEEEES8_x", true)},
|
||||||
|
{Name: symbol.AddSymbolPrefixUnder("ZNK9INIReader7GetRealERKNSt3__112basic_stringIcNS0_11char_traitsIcEENS0_9allocatorIcEEEES8_d", true)},
|
||||||
|
{Name: symbol.AddSymbolPrefixUnder("ZNK9INIReader10ParseErrorEv", true)},
|
||||||
|
},
|
||||||
|
headerSymbols: map[string]*parse.SymbolInfo{
|
||||||
|
"_ZNK9INIReader12GetInteger64ERKNSt3__112basic_stringIcNS0_11char_traitsIcEENS0_9allocatorIcEEEES8_x": {GoName: "(*Reader).GetInteger64", ProtoName: "INIReader::GetInteger64(const std::string &, const std::string &, int64_t)"},
|
||||||
|
"_ZNK9INIReader13GetUnsigned64ERKNSt3__112basic_stringIcNS0_11char_traitsIcEENS0_9allocatorIcEEEES8_y": {GoName: "(*Reader).GetUnsigned64", ProtoName: "INIReader::GetUnsigned64(const std::string &, const std::string &, uint64_t)"},
|
||||||
|
"_ZNK9INIReader7GetRealERKNSt3__112basic_stringIcNS0_11char_traitsIcEENS0_9allocatorIcEEEES8_d": {GoName: "(*Reader).GetReal", ProtoName: "INIReader::GetReal(const std::string &, const std::string &, double)"},
|
||||||
|
"_ZNK9INIReader10ParseErrorEv": {GoName: "(*Reader).ParseError", ProtoName: "INIReader::ParseError()"},
|
||||||
|
"_ZNK9INIReader10GetBooleanERKNSt3__112basic_stringIcNS0_11char_traitsIcEENS0_9allocatorIcEEEES8_b": {GoName: "(*Reader).GetBoolean", ProtoName: "INIReader::GetBoolean(const std::string &, const std::string &, bool)"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
fmt.Printf("\nTest Case: %s\n", tc.name)
|
||||||
|
commonSymbols := symbol.GetCommonSymbols(tc.dylibSymbols, tc.headerSymbols)
|
||||||
|
fmt.Printf("Common Symbols (%d):\n", len(commonSymbols))
|
||||||
|
for _, sym := range commonSymbols {
|
||||||
|
fmt.Printf("Mangle: %s, CPP: %s, Go: %s\n", sym.Mangle, sym.CPP, sym.Go)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt.Println()
|
||||||
|
}
|
||||||
|
func TestReadExistingSymbolTable() {
|
||||||
|
fmt.Println("=== Test ReadExistingSymbolTable ===")
|
||||||
|
|
||||||
|
tmpFile, err := os.CreateTemp("", "llcppg.symb.json")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Failed to create temp file: %v\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer os.Remove(tmpFile.Name())
|
||||||
|
|
||||||
|
testData := `[
|
||||||
|
{
|
||||||
|
"mangle": "_ZN9INIReaderC1EPKcm",
|
||||||
|
"c++": "INIReader::INIReader(const char *, size_t)",
|
||||||
|
"go": "(*Reader).Init__1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mangle": "_ZNK9INIReader10GetBooleanERKNSt3__112basic_stringIcNS0_11char_traitsIcEENS0_9allocatorIcEEEES8_b",
|
||||||
|
"c++": "INIReader::GetBoolean(const std::string &, const std::string &, bool)",
|
||||||
|
"go": "(*Reader).GetBoolean"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mangle": "_ZNK9INIReader10ParseErrorEv",
|
||||||
|
"c++": "INIReader::ParseError()",
|
||||||
|
"go": "(*Reader).ParseError"
|
||||||
|
}
|
||||||
|
]`
|
||||||
|
if _, err := tmpFile.Write([]byte(testData)); err != nil {
|
||||||
|
fmt.Printf("Failed to write test data: %v\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
tmpFile.Close()
|
||||||
|
|
||||||
|
symbols, exist := symbol.ReadExistingSymbolTable(tmpFile.Name())
|
||||||
|
if !exist {
|
||||||
|
fmt.Printf("ReadExistingSymbolTable failed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("Symbols read from the file:")
|
||||||
|
var keys []string
|
||||||
|
for key := range symbols {
|
||||||
|
keys = append(keys, key)
|
||||||
|
}
|
||||||
|
sort.Strings(keys)
|
||||||
|
|
||||||
|
for _, key := range keys {
|
||||||
|
info := symbols[key]
|
||||||
|
fmt.Printf("Symbol Map GoName: %s, ProtoName In HeaderFile: %s, MangledName: %s\n",
|
||||||
|
info.Go, info.CPP, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, exist = symbol.ReadExistingSymbolTable("other.json")
|
||||||
|
if !exist {
|
||||||
|
fmt.Println("Havent existed symb file")
|
||||||
|
}
|
||||||
|
fmt.Println()
|
||||||
|
}
|
||||||
|
func TestGenSymbolTableData() {
|
||||||
|
fmt.Println("=== Test GenSymbolTableData ===")
|
||||||
|
|
||||||
|
commonSymbols := []*types.SymbolInfo{
|
||||||
|
{Mangle: "lua_absindex", CPP: "lua_absindex(lua_State *, int)", Go: "Absindex"},
|
||||||
|
{Mangle: "lua_arith", CPP: "lua_arith(lua_State *, int)", Go: "Arith"},
|
||||||
|
{Mangle: "lua_atpanic", CPP: "lua_atpanic(lua_State *, lua_CFunction)", Go: "Atpanic"},
|
||||||
|
{Mangle: "lua_callk", CPP: "lua_callk(lua_State *, int, int, lua_KContext, lua_KFunction)", Go: "Callk"},
|
||||||
|
}
|
||||||
|
|
||||||
|
existingSymbols := map[string]types.SymbolInfo{
|
||||||
|
"lua_absindex": {Mangle: "lua_absindex", CPP: "lua_absindex(lua_State *, int)", Go: "Absindex"},
|
||||||
|
"lua_arith": {Mangle: "lua_arith", CPP: "lua_arith(lua_State *, int)", Go: "Arith"},
|
||||||
|
"lua_callk": {Mangle: "lua_callk", CPP: "lua_callk(lua_State *, int, int, lua_KContext, lua_KFunction)", Go: "ModifiedCallk"},
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := symbol.GenSymbolTableData(commonSymbols, existingSymbols)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error generating symbol table data: %v\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Println(string(data))
|
||||||
|
fmt.Println()
|
||||||
|
}
|
||||||
45
chore/_xtool/llcppsymg/_cmptest/symg_test/llgo.expect
Normal file
45
chore/_xtool/llcppsymg/_cmptest/symg_test/llgo.expect
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
#stdout
|
||||||
|
=== Test Case: inireader ===
|
||||||
|
[{
|
||||||
|
"mangle": "_ZN9INIReaderC1EPKc",
|
||||||
|
"c++": "INIReader::INIReader(const char *)",
|
||||||
|
"go": "(*Reader).Init"
|
||||||
|
}, {
|
||||||
|
"mangle": "_ZN9INIReaderC1EPKcl",
|
||||||
|
"c++": "INIReader::INIReader(const char *, long)",
|
||||||
|
"go": "(*Reader).Init__1"
|
||||||
|
}, {
|
||||||
|
"mangle": "_ZN9INIReaderD1Ev",
|
||||||
|
"c++": "INIReader::~INIReader()",
|
||||||
|
"go": "(*Reader).Dispose"
|
||||||
|
}, {
|
||||||
|
"mangle": "_ZNK9INIReader10ParseErrorEv",
|
||||||
|
"c++": "INIReader::ParseError()",
|
||||||
|
"go": "(*Reader).ModifyedParseError"
|
||||||
|
}, {
|
||||||
|
"mangle": "_ZNK9INIReader3GetEPKcS1_S1_",
|
||||||
|
"c++": "INIReader::Get(const char *, const char *, const char *)",
|
||||||
|
"go": "(*Reader).Get"
|
||||||
|
}]
|
||||||
|
=== Test Case: lua ===
|
||||||
|
[{
|
||||||
|
"mangle": "lua_error",
|
||||||
|
"c++": "lua_error(lua_State *)",
|
||||||
|
"go": "Error"
|
||||||
|
}, {
|
||||||
|
"mangle": "lua_next",
|
||||||
|
"c++": "lua_next(lua_State *, int)",
|
||||||
|
"go": "Next"
|
||||||
|
}, {
|
||||||
|
"mangle": "lua_concat",
|
||||||
|
"c++": "lua_concat(lua_State *, int)",
|
||||||
|
"go": "Concat"
|
||||||
|
}, {
|
||||||
|
"mangle": "lua_stringtonumber",
|
||||||
|
"c++": "lua_stringtonumber(lua_State *, const char *)",
|
||||||
|
"go": "Stringtonumber"
|
||||||
|
}]
|
||||||
|
|
||||||
|
#stderr
|
||||||
|
|
||||||
|
#exit 0
|
||||||
117
chore/_xtool/llcppsymg/_cmptest/symg_test/symg.go
Normal file
117
chore/_xtool/llcppsymg/_cmptest/symg_test/symg.go
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/goplus/llgo/chore/_xtool/llcppsymg/parse"
|
||||||
|
"github.com/goplus/llgo/chore/_xtool/llcppsymg/symbol"
|
||||||
|
"github.com/goplus/llgo/xtool/nm"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
TestParseHeaderFile()
|
||||||
|
}
|
||||||
|
func TestParseHeaderFile() {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
content string
|
||||||
|
isCpp bool
|
||||||
|
prefixes []string
|
||||||
|
dylibSymbols []*nm.Symbol
|
||||||
|
symbFileContent string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "inireader",
|
||||||
|
content: `
|
||||||
|
#define INI_API __attribute__((visibility("default")))
|
||||||
|
class INIReader {
|
||||||
|
public:
|
||||||
|
__attribute__((visibility("default"))) explicit INIReader(const char *filename);
|
||||||
|
INI_API explicit INIReader(const char *buffer, long buffer_size);
|
||||||
|
~INIReader();
|
||||||
|
INI_API int ParseError() const;
|
||||||
|
INI_API const char * Get(const char *section, const char *name,
|
||||||
|
const char *default_value) const;
|
||||||
|
private:
|
||||||
|
static const char * MakeKey(const char *section, const char *name);
|
||||||
|
};
|
||||||
|
`,
|
||||||
|
isCpp: true,
|
||||||
|
prefixes: []string{"INI"},
|
||||||
|
dylibSymbols: []*nm.Symbol{
|
||||||
|
{Name: symbol.AddSymbolPrefixUnder("ZN9INIReaderC1EPKc", true)},
|
||||||
|
{Name: symbol.AddSymbolPrefixUnder("ZN9INIReaderC1EPKcl", true)},
|
||||||
|
{Name: symbol.AddSymbolPrefixUnder("ZN9INIReaderD1Ev", true)},
|
||||||
|
{Name: symbol.AddSymbolPrefixUnder("ZNK9INIReader10ParseErrorEv", true)},
|
||||||
|
{Name: symbol.AddSymbolPrefixUnder("ZNK9INIReader3GetEPKcS1_S1_", true)},
|
||||||
|
},
|
||||||
|
symbFileContent: `
|
||||||
|
[{
|
||||||
|
"mangle": "_ZN9INIReaderC1EPKc",
|
||||||
|
"c++": "INIReader::INIReader(const char *)",
|
||||||
|
"go": "(*Reader).Init"
|
||||||
|
}, {
|
||||||
|
"mangle": "_ZN9INIReaderC1EPKcl",
|
||||||
|
"c++": "INIReader::INIReader(const char *, long)",
|
||||||
|
"go": "(*Reader).Init__1"
|
||||||
|
}, {
|
||||||
|
"mangle": "_ZN9INIReaderD1Ev",
|
||||||
|
"c++": "INIReader::~INIReader()",
|
||||||
|
"go": "(*Reader).Dispose"
|
||||||
|
}, {
|
||||||
|
"mangle": "_ZNK9INIReader10ParseErrorEv",
|
||||||
|
"c++": "INIReader::ParseError()",
|
||||||
|
"go": "(*Reader).ModifyedParseError"
|
||||||
|
}]`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "lua",
|
||||||
|
content: `
|
||||||
|
typedef struct lua_State lua_State;
|
||||||
|
|
||||||
|
LUA_API int(lua_error)(lua_State *L);
|
||||||
|
|
||||||
|
LUA_API int(lua_next)(lua_State *L, int idx);
|
||||||
|
|
||||||
|
LUA_API void(lua_concat)(lua_State *L, int n);
|
||||||
|
LUA_API void(lua_len)(lua_State *L, int idx);
|
||||||
|
|
||||||
|
LUA_API long unsigned int(lua_stringtonumber)(lua_State *L, const char *s);
|
||||||
|
|
||||||
|
LUA_API void(lua_setallocf)(lua_State *L, lua_Alloc f, void *ud);
|
||||||
|
|
||||||
|
LUA_API void(lua_toclose)(lua_State *L, int idx);
|
||||||
|
LUA_API void(lua_closeslot)(lua_State *L, int idx);
|
||||||
|
`,
|
||||||
|
isCpp: false,
|
||||||
|
prefixes: []string{"lua_"},
|
||||||
|
dylibSymbols: []*nm.Symbol{
|
||||||
|
{Name: symbol.AddSymbolPrefixUnder("lua_error", false)},
|
||||||
|
{Name: symbol.AddSymbolPrefixUnder("lua_next", false)},
|
||||||
|
{Name: symbol.AddSymbolPrefixUnder("lua_concat", false)},
|
||||||
|
{Name: symbol.AddSymbolPrefixUnder("lua_stringtonumber", false)},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
fmt.Printf("=== Test Case: %s ===\n", tc.name)
|
||||||
|
headerSymbolMap, err := parse.ParseHeaderFile([]string{tc.content}, tc.prefixes, tc.isCpp, true)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Error:", err)
|
||||||
|
}
|
||||||
|
tmpFile, err := os.CreateTemp("", "llcppg.symb.json")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Failed to create temp file: %v\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
tmpFile.Write([]byte(tc.symbFileContent))
|
||||||
|
symbolData, err := symbol.GenerateAndUpdateSymbolTable(tc.dylibSymbols, headerSymbolMap, tmpFile.Name())
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Error:", err)
|
||||||
|
}
|
||||||
|
fmt.Println(string(symbolData))
|
||||||
|
os.Remove(tmpFile.Name())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,6 +16,10 @@ type Config struct {
|
|||||||
Index *clang.Index
|
Index *clang.Index
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Visitor func(cursor, parent clang.Cursor) clang.ChildVisitResult
|
||||||
|
|
||||||
|
const TEMP_FILE = "temp.h"
|
||||||
|
|
||||||
func CreateTranslationUnit(config *Config) (*clang.Index, *clang.TranslationUnit, error) {
|
func CreateTranslationUnit(config *Config) (*clang.Index, *clang.TranslationUnit, error) {
|
||||||
// default use the c/c++ standard of clang; c:gnu17 c++:gnu++17
|
// default use the c/c++ standard of clang; c:gnu17 c++:gnu++17
|
||||||
// https://clang.llvm.org/docs/CommandGuide/clang.html
|
// https://clang.llvm.org/docs/CommandGuide/clang.html
|
||||||
@@ -42,7 +46,7 @@ func CreateTranslationUnit(config *Config) (*clang.Index, *clang.TranslationUnit
|
|||||||
if config.Temp {
|
if config.Temp {
|
||||||
content := c.AllocaCStr(config.File)
|
content := c.AllocaCStr(config.File)
|
||||||
tempFile := &clang.UnsavedFile{
|
tempFile := &clang.UnsavedFile{
|
||||||
Filename: c.Str("temp.h"),
|
Filename: c.Str(TEMP_FILE),
|
||||||
Contents: content,
|
Contents: content,
|
||||||
Length: c.Ulong(c.Strlen(content)),
|
Length: c.Ulong(c.Strlen(content)),
|
||||||
}
|
}
|
||||||
@@ -71,6 +75,11 @@ func CreateTranslationUnit(config *Config) (*clang.Index, *clang.TranslationUnit
|
|||||||
return index, unit, nil
|
return index, unit, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetLocation(loc clang.SourceLocation) (file clang.File, line c.Uint, column c.Uint, offset c.Uint) {
|
||||||
|
loc.SpellingLocation(&file, &line, &column, &offset)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Traverse up the semantic parents
|
// Traverse up the semantic parents
|
||||||
func BuildScopingParts(cursor clang.Cursor) []string {
|
func BuildScopingParts(cursor clang.Cursor) []string {
|
||||||
var parts []string
|
var parts []string
|
||||||
@@ -83,3 +92,10 @@ func BuildScopingParts(cursor clang.Cursor) []string {
|
|||||||
}
|
}
|
||||||
return parts
|
return parts
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func VisitChildren(cursor clang.Cursor, fn Visitor) c.Uint {
|
||||||
|
return clang.VisitChildren(cursor, func(cursor, parent clang.Cursor, clientData unsafe.Pointer) clang.ChildVisitResult {
|
||||||
|
cfn := *(*Visitor)(clientData)
|
||||||
|
return cfn(cursor, parent)
|
||||||
|
}, unsafe.Pointer(&fn))
|
||||||
|
}
|
||||||
|
|||||||
109
chore/_xtool/llcppsymg/config/cfgparse/parse.go
Normal file
109
chore/_xtool/llcppsymg/config/cfgparse/parse.go
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
package cfgparse
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Note: This package is not placed under the 'config' package because 'config'
|
||||||
|
// depends on 'cjson'. The parsing of Libs and cflags is intended to be usable
|
||||||
|
// by both llgo and go, without introducing additional dependencies.
|
||||||
|
|
||||||
|
type Libs struct {
|
||||||
|
Paths []string // Dylib Path
|
||||||
|
Names []string
|
||||||
|
}
|
||||||
|
|
||||||
|
type CFlags struct {
|
||||||
|
Paths []string // Include Path
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseLibs(libs string) *Libs {
|
||||||
|
parts := strings.Fields(libs)
|
||||||
|
lbs := &Libs{}
|
||||||
|
for _, part := range parts {
|
||||||
|
if strings.HasPrefix(part, "-L") {
|
||||||
|
lbs.Paths = append(lbs.Paths, part[2:])
|
||||||
|
} else if strings.HasPrefix(part, "-l") {
|
||||||
|
lbs.Names = append(lbs.Names, part[2:])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return lbs
|
||||||
|
}
|
||||||
|
|
||||||
|
// searches for each library name in the provided paths and default paths,
|
||||||
|
// appending the appropriate file extension (.dylib for macOS, .so for Linux).
|
||||||
|
//
|
||||||
|
// Example: For "-L/opt/homebrew/lib -llua -lm":
|
||||||
|
// - It will search for liblua.dylib (on macOS) or liblua.so (on Linux)
|
||||||
|
// - System libs like -lm are ignored and included in notFound
|
||||||
|
//
|
||||||
|
// So error is returned if no libraries found at all.
|
||||||
|
func (l *Libs) GenDylibPaths(defaultPaths []string) ([]string, []string, error) {
|
||||||
|
var foundPaths []string
|
||||||
|
var notFound []string
|
||||||
|
affix := ".dylib"
|
||||||
|
if runtime.GOOS == "linux" {
|
||||||
|
affix = ".so"
|
||||||
|
}
|
||||||
|
searchPaths := append(l.Paths, defaultPaths...)
|
||||||
|
for _, name := range l.Names {
|
||||||
|
var foundPath string
|
||||||
|
for _, path := range searchPaths {
|
||||||
|
dylibPath := filepath.Join(path, "lib"+name+affix)
|
||||||
|
if _, err := os.Stat(dylibPath); err == nil {
|
||||||
|
foundPath = dylibPath
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if foundPath != "" {
|
||||||
|
foundPaths = append(foundPaths, foundPath)
|
||||||
|
} else {
|
||||||
|
notFound = append(notFound, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(foundPaths) == 0 {
|
||||||
|
return nil, notFound, fmt.Errorf("failed to find any libraries")
|
||||||
|
}
|
||||||
|
return foundPaths, notFound, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseCFlags(cflags string) *CFlags {
|
||||||
|
parts := strings.Fields(cflags)
|
||||||
|
cf := &CFlags{}
|
||||||
|
for _, part := range parts {
|
||||||
|
if strings.HasPrefix(part, "-I") {
|
||||||
|
cf.Paths = append(cf.Paths, part[2:])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cf
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cf *CFlags) GenHeaderFilePaths(files []string) ([]string, []string, error) {
|
||||||
|
var foundPaths []string
|
||||||
|
var notFound []string
|
||||||
|
|
||||||
|
for _, file := range files {
|
||||||
|
var found bool
|
||||||
|
for _, path := range cf.Paths {
|
||||||
|
fullPath := filepath.Join(path, file)
|
||||||
|
if _, err := os.Stat(fullPath); err == nil {
|
||||||
|
foundPaths = append(foundPaths, fullPath)
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
notFound = append(notFound, file)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(foundPaths) == 0 {
|
||||||
|
return nil, notFound, fmt.Errorf("failed to find any header files")
|
||||||
|
}
|
||||||
|
|
||||||
|
return foundPaths, notFound, nil
|
||||||
|
}
|
||||||
@@ -17,55 +17,74 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
"github.com/goplus/llgo/c"
|
"github.com/goplus/llgo/chore/_xtool/llcppsymg/args"
|
||||||
"github.com/goplus/llgo/c/cjson"
|
|
||||||
"github.com/goplus/llgo/chore/_xtool/llcppsymg/config"
|
"github.com/goplus/llgo/chore/_xtool/llcppsymg/config"
|
||||||
|
"github.com/goplus/llgo/chore/_xtool/llcppsymg/config/cfgparse"
|
||||||
"github.com/goplus/llgo/chore/_xtool/llcppsymg/parse"
|
"github.com/goplus/llgo/chore/_xtool/llcppsymg/parse"
|
||||||
"github.com/goplus/llgo/chore/llcppg/types"
|
"github.com/goplus/llgo/chore/_xtool/llcppsymg/symbol"
|
||||||
"github.com/goplus/llgo/xtool/nm"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
cfgFile := "llcppg.cfg"
|
symbFile := "llcppg.symb.json"
|
||||||
if len(os.Args) > 1 {
|
|
||||||
cfgFile = os.Args[1]
|
ags, _ := args.ParseArgs(os.Args[1:], nil)
|
||||||
}
|
|
||||||
|
|
||||||
var data []byte
|
var data []byte
|
||||||
var err error
|
var err error
|
||||||
if cfgFile == "-" {
|
if ags.UseStdin {
|
||||||
data, err = io.ReadAll(os.Stdin)
|
data, err = io.ReadAll(os.Stdin)
|
||||||
} else {
|
} else {
|
||||||
data, err = os.ReadFile(cfgFile)
|
data, err = os.ReadFile(ags.CfgFile)
|
||||||
}
|
}
|
||||||
check(err)
|
|
||||||
|
|
||||||
|
check(err)
|
||||||
conf, err := config.GetConf(data)
|
conf, err := config.GetConf(data)
|
||||||
check(err)
|
check(err)
|
||||||
defer conf.Delete()
|
defer conf.Delete()
|
||||||
|
|
||||||
if err != nil {
|
if ags.Verbose {
|
||||||
fmt.Fprintln(os.Stderr, "Failed to parse config file:", cfgFile)
|
symbol.SetDebug(symbol.DbgFlagAll)
|
||||||
|
if ags.UseStdin {
|
||||||
|
fmt.Println("Config From Stdin")
|
||||||
|
} else {
|
||||||
|
fmt.Println("Config From File", ags.CfgFile)
|
||||||
|
}
|
||||||
|
fmt.Println("Name:", conf.Name)
|
||||||
|
fmt.Println("CFlags:", conf.CFlags)
|
||||||
|
fmt.Println("Libs:", conf.Libs)
|
||||||
|
fmt.Println("Include:", conf.Include)
|
||||||
|
fmt.Println("TrimPrefixes:", conf.TrimPrefixes)
|
||||||
|
fmt.Println("Cplusplus:", conf.Cplusplus)
|
||||||
}
|
}
|
||||||
symbols, err := parseDylibSymbols(conf.Libs)
|
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, "Failed to parse config file:", ags.CfgFile)
|
||||||
|
}
|
||||||
|
symbols, err := symbol.ParseDylibSymbols(conf.Libs)
|
||||||
check(err)
|
check(err)
|
||||||
|
|
||||||
filepaths := genHeaderFilePath(conf.CFlags, conf.Include)
|
cflag := cfgparse.ParseCFlags(conf.CFlags)
|
||||||
headerInfos, err := parse.ParseHeaderFile(filepaths, conf.TrimPrefixes, conf.Cplusplus)
|
filepaths, notFounds, err := cflag.GenHeaderFilePaths(conf.Include)
|
||||||
check(err)
|
check(err)
|
||||||
|
|
||||||
symbolInfo := getCommonSymbols(symbols, headerInfos, conf.TrimPrefixes)
|
if ags.Verbose {
|
||||||
|
fmt.Println("header file paths", filepaths)
|
||||||
|
if len(notFounds) > 0 {
|
||||||
|
fmt.Println("not found header files", notFounds)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
err = genSymbolTableFile(symbolInfo)
|
headerInfos, err := parse.ParseHeaderFile(filepaths, conf.TrimPrefixes, conf.Cplusplus, false)
|
||||||
|
check(err)
|
||||||
|
|
||||||
|
symbolData, err := symbol.GenerateAndUpdateSymbolTable(symbols, headerInfos, symbFile)
|
||||||
|
check(err)
|
||||||
|
|
||||||
|
err = os.WriteFile(symbFile, symbolData, 0644)
|
||||||
check(err)
|
check(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,141 +93,3 @@ func check(err error) {
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseDylibSymbols(lib string) ([]*nm.Symbol, error) {
|
|
||||||
dylibPath, err := genDylibPath(lib)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.New("failed to generate dylib path")
|
|
||||||
}
|
|
||||||
|
|
||||||
files, err := nm.New("").List(dylibPath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.New("failed to list symbols in dylib")
|
|
||||||
}
|
|
||||||
|
|
||||||
var symbols []*nm.Symbol
|
|
||||||
for _, file := range files {
|
|
||||||
symbols = append(symbols, file.Symbols...)
|
|
||||||
}
|
|
||||||
|
|
||||||
return symbols, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func genDylibPath(lib string) (string, error) {
|
|
||||||
output := lib
|
|
||||||
libPath := ""
|
|
||||||
libName := ""
|
|
||||||
for _, part := range strings.Fields(string(output)) {
|
|
||||||
if strings.HasPrefix(part, "-L") {
|
|
||||||
libPath = part[2:]
|
|
||||||
} else if strings.HasPrefix(part, "-l") {
|
|
||||||
libName = part[2:]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if libPath == "" || libName == "" {
|
|
||||||
return "", fmt.Errorf("failed to parse pkg-config output: %s", output)
|
|
||||||
}
|
|
||||||
|
|
||||||
dylibPath := filepath.Join(libPath, "lib"+libName+".dylib")
|
|
||||||
return dylibPath, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func genHeaderFilePath(cflags string, files []string) []string {
|
|
||||||
prefixPath := cflags
|
|
||||||
prefixPath = strings.TrimPrefix(prefixPath, "-I")
|
|
||||||
var includePaths []string
|
|
||||||
for _, file := range files {
|
|
||||||
includePaths = append(includePaths, filepath.Join(prefixPath, "/"+file))
|
|
||||||
}
|
|
||||||
return includePaths
|
|
||||||
}
|
|
||||||
|
|
||||||
func getCommonSymbols(dylibSymbols []*nm.Symbol, symbolMap map[string]*parse.SymbolInfo, prefix []string) []*types.SymbolInfo {
|
|
||||||
var commonSymbols []*types.SymbolInfo
|
|
||||||
for _, dylibSym := range dylibSymbols {
|
|
||||||
symName := strings.TrimPrefix(dylibSym.Name, "_")
|
|
||||||
if symInfo, ok := symbolMap[symName]; ok {
|
|
||||||
symbolInfo := &types.SymbolInfo{
|
|
||||||
Mangle: symName,
|
|
||||||
CPP: symInfo.ProtoName,
|
|
||||||
Go: symInfo.GoName,
|
|
||||||
}
|
|
||||||
commonSymbols = append(commonSymbols, symbolInfo)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return commonSymbols
|
|
||||||
}
|
|
||||||
|
|
||||||
func genSymbolTableFile(symbolInfos []*types.SymbolInfo) error {
|
|
||||||
fileName := "llcppg.symb.json"
|
|
||||||
existingSymbols, err := readExistingSymbolTable(fileName)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := range symbolInfos {
|
|
||||||
if existingSymbol, exists := existingSymbols[symbolInfos[i].Mangle]; exists {
|
|
||||||
symbolInfos[i].Go = existingSymbol.Go
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
root := cjson.Array()
|
|
||||||
defer root.Delete()
|
|
||||||
|
|
||||||
for _, symbol := range symbolInfos {
|
|
||||||
item := cjson.Object()
|
|
||||||
item.SetItem(c.Str("mangle"), cjson.String(c.AllocaCStr(symbol.Mangle)))
|
|
||||||
item.SetItem(c.Str("c++"), cjson.String(c.AllocaCStr(symbol.CPP)))
|
|
||||||
item.SetItem(c.Str("go"), cjson.String(c.AllocaCStr(symbol.Go)))
|
|
||||||
root.AddItem(item)
|
|
||||||
}
|
|
||||||
|
|
||||||
cStr := root.Print()
|
|
||||||
if cStr == nil {
|
|
||||||
return errors.New("symbol table is empty")
|
|
||||||
}
|
|
||||||
defer c.Free(unsafe.Pointer(cStr))
|
|
||||||
|
|
||||||
data := unsafe.Slice((*byte)(unsafe.Pointer(cStr)), c.Strlen(cStr))
|
|
||||||
|
|
||||||
if err := os.WriteFile(fileName, data, 0644); err != nil {
|
|
||||||
return errors.New("failed to write symbol table file")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func readExistingSymbolTable(fileName string) (map[string]types.SymbolInfo, error) {
|
|
||||||
existingSymbols := make(map[string]types.SymbolInfo)
|
|
||||||
|
|
||||||
if _, err := os.Stat(fileName); err != nil {
|
|
||||||
return existingSymbols, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
data, err := os.ReadFile(fileName)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.New("failed to read symbol table file")
|
|
||||||
}
|
|
||||||
|
|
||||||
parsedJSON := cjson.ParseBytes(data)
|
|
||||||
if parsedJSON == nil {
|
|
||||||
return nil, errors.New("failed to parse JSON")
|
|
||||||
}
|
|
||||||
|
|
||||||
arraySize := parsedJSON.GetArraySize()
|
|
||||||
|
|
||||||
for i := 0; i < int(arraySize); i++ {
|
|
||||||
item := parsedJSON.GetArrayItem(c.Int(i))
|
|
||||||
if item == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
symbol := types.SymbolInfo{
|
|
||||||
Mangle: config.GetStringItem(item, "mangle", ""),
|
|
||||||
CPP: config.GetStringItem(item, "c++", ""),
|
|
||||||
Go: config.GetStringItem(item, "go", ""),
|
|
||||||
}
|
|
||||||
existingSymbols[symbol.Mangle] = symbol
|
|
||||||
}
|
|
||||||
|
|
||||||
return existingSymbols, nil
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package parse
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -15,37 +16,27 @@ type SymbolInfo struct {
|
|||||||
ProtoName string
|
ProtoName string
|
||||||
}
|
}
|
||||||
|
|
||||||
type Context struct {
|
type SymbolProcessor struct {
|
||||||
namespaceName string
|
Prefixes []string
|
||||||
className string
|
SymbolMap map[string]*SymbolInfo
|
||||||
prefixes []string
|
CurrentFile string
|
||||||
symbolMap map[string]*SymbolInfo
|
NameCounts map[string]int
|
||||||
currentFile string
|
|
||||||
nameCounts map[string]int
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func newContext(prefixes []string) *Context {
|
func NewSymbolProcessor(Prefixes []string) *SymbolProcessor {
|
||||||
return &Context{
|
return &SymbolProcessor{
|
||||||
prefixes: prefixes,
|
Prefixes: Prefixes,
|
||||||
symbolMap: make(map[string]*SymbolInfo),
|
SymbolMap: make(map[string]*SymbolInfo),
|
||||||
nameCounts: make(map[string]int),
|
NameCounts: make(map[string]int),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Context) setNamespaceName(name string) {
|
func (p *SymbolProcessor) setCurrentFile(filename string) {
|
||||||
c.namespaceName = name
|
p.CurrentFile = filename
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Context) setClassName(name string) {
|
func (p *SymbolProcessor) TrimPrefixes(str string) string {
|
||||||
c.className = name
|
for _, prefix := range p.Prefixes {
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Context) setCurrentFile(filename string) {
|
|
||||||
c.currentFile = filename
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Context) removePrefix(str string) string {
|
|
||||||
for _, prefix := range c.prefixes {
|
|
||||||
if strings.HasPrefix(str, prefix) {
|
if strings.HasPrefix(str, prefix) {
|
||||||
return strings.TrimPrefix(str, prefix)
|
return strings.TrimPrefix(str, prefix)
|
||||||
}
|
}
|
||||||
@@ -57,10 +48,10 @@ func toTitle(s string) string {
|
|||||||
if s == "" {
|
if s == "" {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
return strings.ToUpper(s[:1]) + strings.ToLower(s[1:])
|
return strings.ToUpper(s[:1]) + (s[1:])
|
||||||
}
|
}
|
||||||
|
|
||||||
func toCamel(originName string) string {
|
func toUpperCamelCase(originName string) string {
|
||||||
if originName == "" {
|
if originName == "" {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
@@ -74,37 +65,44 @@ func toCamel(originName string) string {
|
|||||||
|
|
||||||
// 1. remove prefix from config
|
// 1. remove prefix from config
|
||||||
// 2. convert to camel case
|
// 2. convert to camel case
|
||||||
func (c *Context) toGoName(name string) string {
|
func (p *SymbolProcessor) ToGoName(name string) string {
|
||||||
name = c.removePrefix(name)
|
return toUpperCamelCase(p.TrimPrefixes(name))
|
||||||
return toCamel(name)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Context) genGoName(name string) string {
|
func (p *SymbolProcessor) GenMethodName(class, name string, isDestructor bool) string {
|
||||||
class := c.toGoName(c.className)
|
|
||||||
name = c.toGoName(name)
|
|
||||||
|
|
||||||
var baseName string
|
|
||||||
if class == "" {
|
|
||||||
baseName = name
|
|
||||||
} else {
|
|
||||||
baseName = c.genMethodName(class, name)
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.addSuffix(baseName)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Context) genMethodName(class, name string) string {
|
|
||||||
prefix := "(*" + class + ")."
|
prefix := "(*" + class + ")."
|
||||||
|
if isDestructor {
|
||||||
|
return prefix + "Dispose"
|
||||||
|
}
|
||||||
if class == name {
|
if class == name {
|
||||||
return prefix + "Init"
|
return prefix + "Init"
|
||||||
}
|
}
|
||||||
if name == "~"+class {
|
|
||||||
return prefix + "Dispose"
|
|
||||||
}
|
|
||||||
return prefix + name
|
return prefix + name
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Context) genProtoName(cursor clang.Cursor) string {
|
func (p *SymbolProcessor) genGoName(cursor clang.Cursor) string {
|
||||||
|
funcName := cursor.String()
|
||||||
|
defer funcName.Dispose()
|
||||||
|
|
||||||
|
originName := c.GoString(funcName.CStr())
|
||||||
|
isDestructor := cursor.Kind == clang.CursorDestructor
|
||||||
|
var convertedName string
|
||||||
|
if isDestructor {
|
||||||
|
convertedName = p.ToGoName(originName[1:])
|
||||||
|
} else {
|
||||||
|
convertedName = p.ToGoName(originName)
|
||||||
|
}
|
||||||
|
|
||||||
|
if parent := cursor.SemanticParent(); parent.Kind == clang.CursorClassDecl {
|
||||||
|
parentName := parent.String()
|
||||||
|
defer parentName.Dispose()
|
||||||
|
class := p.ToGoName(c.GoString(parentName.CStr()))
|
||||||
|
return p.AddSuffix(p.GenMethodName(class, convertedName, isDestructor))
|
||||||
|
}
|
||||||
|
return p.AddSuffix(convertedName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *SymbolProcessor) genProtoName(cursor clang.Cursor) string {
|
||||||
displayName := cursor.DisplayName()
|
displayName := cursor.DisplayName()
|
||||||
defer displayName.Dispose()
|
defer displayName.Dispose()
|
||||||
|
|
||||||
@@ -120,95 +118,73 @@ func (p *Context) genProtoName(cursor clang.Cursor) string {
|
|||||||
return builder.String()
|
return builder.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Context) addSuffix(name string) string {
|
func (p *SymbolProcessor) AddSuffix(name string) string {
|
||||||
c.nameCounts[name]++
|
p.NameCounts[name]++
|
||||||
count := c.nameCounts[name]
|
if count := p.NameCounts[name]; count > 1 {
|
||||||
if count > 1 {
|
|
||||||
return name + "__" + strconv.Itoa(count-1)
|
return name + "__" + strconv.Itoa(count-1)
|
||||||
}
|
}
|
||||||
return name
|
return name
|
||||||
}
|
}
|
||||||
|
|
||||||
var context = newContext([]string{})
|
func (p *SymbolProcessor) collectFuncInfo(cursor clang.Cursor) {
|
||||||
|
|
||||||
func collectFuncInfo(cursor clang.Cursor) {
|
|
||||||
cursorStr := cursor.String()
|
|
||||||
symbol := cursor.Mangling()
|
symbol := cursor.Mangling()
|
||||||
|
|
||||||
name := c.GoString(cursorStr.CStr())
|
|
||||||
symbolName := c.GoString(symbol.CStr())
|
|
||||||
if len(symbolName) >= 1 && symbolName[0] == '_' {
|
|
||||||
symbolName = symbolName[1:]
|
|
||||||
}
|
|
||||||
defer symbol.Dispose()
|
defer symbol.Dispose()
|
||||||
defer cursorStr.Dispose()
|
|
||||||
|
|
||||||
context.symbolMap[symbolName] = &SymbolInfo{
|
// On Linux, C++ symbols typically have one leading underscore
|
||||||
GoName: context.genGoName(name),
|
// On macOS, C++ symbols may have two leading underscores
|
||||||
ProtoName: context.genProtoName(cursor),
|
// For consistency, we remove the first leading underscore on macOS
|
||||||
|
symbolName := c.GoString(symbol.CStr())
|
||||||
|
if runtime.GOOS == "darwin" {
|
||||||
|
symbolName = strings.TrimPrefix(symbolName, "_")
|
||||||
|
}
|
||||||
|
p.SymbolMap[symbolName] = &SymbolInfo{
|
||||||
|
GoName: p.genGoName(cursor),
|
||||||
|
ProtoName: p.genProtoName(cursor),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func visit(cursor, parent clang.Cursor, clientData c.Pointer) clang.ChildVisitResult {
|
func (p *SymbolProcessor) visitTop(cursor, parent clang.Cursor) clang.ChildVisitResult {
|
||||||
switch cursor.Kind {
|
switch cursor.Kind {
|
||||||
case clang.CursorNamespace, clang.CursorClassDecl:
|
case clang.CursorNamespace, clang.CursorClassDecl:
|
||||||
nameStr := cursor.String()
|
clangutils.VisitChildren(cursor, p.visitTop)
|
||||||
defer nameStr.Dispose()
|
|
||||||
|
|
||||||
name := c.GoString(nameStr.CStr())
|
|
||||||
if cursor.Kind == clang.CursorNamespace {
|
|
||||||
context.setNamespaceName(name)
|
|
||||||
} else {
|
|
||||||
context.setClassName(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
clang.VisitChildren(cursor, visit, nil)
|
|
||||||
|
|
||||||
if cursor.Kind == clang.CursorNamespace {
|
|
||||||
context.setNamespaceName("")
|
|
||||||
} else {
|
|
||||||
context.setClassName("")
|
|
||||||
}
|
|
||||||
|
|
||||||
case clang.CursorCXXMethod, clang.CursorFunctionDecl, clang.CursorConstructor, clang.CursorDestructor:
|
case clang.CursorCXXMethod, clang.CursorFunctionDecl, clang.CursorConstructor, clang.CursorDestructor:
|
||||||
loc := cursor.Location()
|
loc := cursor.Location()
|
||||||
var file clang.File
|
file, _, _, _ := clangutils.GetLocation(loc)
|
||||||
var line, column c.Uint
|
|
||||||
|
|
||||||
loc.SpellingLocation(&file, &line, &column, nil)
|
|
||||||
filename := file.FileName()
|
filename := file.FileName()
|
||||||
defer filename.Dispose()
|
defer filename.Dispose()
|
||||||
|
|
||||||
isCurrentFile := c.Strcmp(filename.CStr(), c.AllocaCStr(context.currentFile)) == 0
|
isCurrentFile := c.Strcmp(filename.CStr(), c.AllocaCStr(p.CurrentFile)) == 0
|
||||||
isPublicMethod := (cursor.CXXAccessSpecifier() == clang.CXXPublic) && cursor.Kind == clang.CursorCXXMethod || cursor.Kind == clang.CursorConstructor || cursor.Kind == clang.CursorDestructor
|
isPublicMethod := (cursor.CXXAccessSpecifier() == clang.CXXPublic) && cursor.Kind == clang.CursorCXXMethod || cursor.Kind == clang.CursorConstructor || cursor.Kind == clang.CursorDestructor
|
||||||
|
|
||||||
if isCurrentFile && (cursor.Kind == clang.CursorFunctionDecl || isPublicMethod) {
|
if isCurrentFile && (cursor.Kind == clang.CursorFunctionDecl || isPublicMethod) {
|
||||||
collectFuncInfo(cursor)
|
p.collectFuncInfo(cursor)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return clang.ChildVisit_Continue
|
return clang.ChildVisit_Continue
|
||||||
}
|
}
|
||||||
|
|
||||||
func ParseHeaderFile(filepaths []string, prefixes []string, isCpp bool) (map[string]*SymbolInfo, error) {
|
func ParseHeaderFile(files []string, Prefixes []string, isCpp bool, isTemp bool) (map[string]*SymbolInfo, error) {
|
||||||
context = newContext(prefixes)
|
processer := NewSymbolProcessor(Prefixes)
|
||||||
index := clang.CreateIndex(0, 0)
|
index := clang.CreateIndex(0, 0)
|
||||||
for _, filename := range filepaths {
|
for _, file := range files {
|
||||||
_, unit, err := clangutils.CreateTranslationUnit(&clangutils.Config{
|
_, unit, err := clangutils.CreateTranslationUnit(&clangutils.Config{
|
||||||
File: filename,
|
File: file,
|
||||||
Temp: false,
|
Temp: isTemp,
|
||||||
IsCpp: isCpp,
|
IsCpp: isCpp,
|
||||||
Index: index,
|
Index: index,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.New("Unable to parse translation unit for file " + filename)
|
return nil, errors.New("Unable to parse translation unit for file " + file)
|
||||||
}
|
}
|
||||||
|
|
||||||
cursor := unit.Cursor()
|
cursor := unit.Cursor()
|
||||||
context.setCurrentFile(filename)
|
if isTemp {
|
||||||
clang.VisitChildren(cursor, visit, nil)
|
processer.setCurrentFile(clangutils.TEMP_FILE)
|
||||||
|
} else {
|
||||||
|
processer.setCurrentFile(file)
|
||||||
|
}
|
||||||
|
clangutils.VisitChildren(cursor, processer.visitTop)
|
||||||
unit.Dispose()
|
unit.Dispose()
|
||||||
}
|
}
|
||||||
index.Dispose()
|
index.Dispose()
|
||||||
return context.symbolMap, nil
|
return processer.SymbolMap, nil
|
||||||
}
|
}
|
||||||
|
|||||||
283
chore/_xtool/llcppsymg/symbol/symbol.go
Normal file
283
chore/_xtool/llcppsymg/symbol/symbol.go
Normal file
@@ -0,0 +1,283 @@
|
|||||||
|
package symbol
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/goplus/llgo/c"
|
||||||
|
"github.com/goplus/llgo/c/cjson"
|
||||||
|
"github.com/goplus/llgo/chore/_xtool/llcppsymg/config"
|
||||||
|
"github.com/goplus/llgo/chore/_xtool/llcppsymg/config/cfgparse"
|
||||||
|
"github.com/goplus/llgo/chore/_xtool/llcppsymg/parse"
|
||||||
|
"github.com/goplus/llgo/chore/llcppg/types"
|
||||||
|
"github.com/goplus/llgo/xtool/nm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type dbgFlags = int
|
||||||
|
|
||||||
|
const (
|
||||||
|
DbgSymbol dbgFlags = 1 << iota
|
||||||
|
DbgFlagAll = DbgSymbol
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
debugSymbol bool
|
||||||
|
)
|
||||||
|
|
||||||
|
func SetDebug(dbgFlags dbgFlags) {
|
||||||
|
debugSymbol = (dbgFlags & DbgSymbol) != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseDylibSymbols parses symbols from dynamic libraries specified in the lib string.
|
||||||
|
// It handles multiple libraries (e.g., -L/opt/homebrew/lib -llua -lm) and returns
|
||||||
|
// symbols if at least one library is successfully parsed. Errors from inaccessible
|
||||||
|
// libraries (like standard libs) are logged as warnings.
|
||||||
|
//
|
||||||
|
// Returns symbols and nil error if any symbols are found, or nil and error if none found.
|
||||||
|
func ParseDylibSymbols(lib string) ([]*nm.Symbol, error) {
|
||||||
|
if debugSymbol {
|
||||||
|
fmt.Println("ParseDylibSymbols:from", lib)
|
||||||
|
}
|
||||||
|
sysPaths := getSysLibPaths()
|
||||||
|
lbs := cfgparse.ParseLibs(lib)
|
||||||
|
if debugSymbol {
|
||||||
|
fmt.Println("ParseDylibSymbols:LibConfig Parse To")
|
||||||
|
fmt.Println("libs.Names: ", lbs.Names)
|
||||||
|
fmt.Println("libs.Paths: ", lbs.Paths)
|
||||||
|
}
|
||||||
|
dylibPaths, notFounds, err := lbs.GenDylibPaths(sysPaths)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to generate some dylib paths: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if debugSymbol {
|
||||||
|
fmt.Println("ParseDylibSymbols:dylibPaths", dylibPaths)
|
||||||
|
if len(notFounds) > 0 {
|
||||||
|
fmt.Println("ParseDylibSymbols:not found libname", notFounds)
|
||||||
|
} else {
|
||||||
|
fmt.Println("ParseDylibSymbols:every library is found")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var symbols []*nm.Symbol
|
||||||
|
var parseErrors []string
|
||||||
|
|
||||||
|
for _, dylibPath := range dylibPaths {
|
||||||
|
if _, err := os.Stat(dylibPath); err != nil {
|
||||||
|
if debugSymbol {
|
||||||
|
fmt.Printf("ParseDylibSymbols:Failed to access dylib %s: %v\n", dylibPath, err)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
files, err := nm.New("").List(dylibPath)
|
||||||
|
if err != nil {
|
||||||
|
parseErrors = append(parseErrors, fmt.Sprintf("ParseDylibSymbols:Failed to list symbols in dylib %s: %v", dylibPath, err))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, file := range files {
|
||||||
|
symbols = append(symbols, file.Symbols...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(symbols) > 0 {
|
||||||
|
if debugSymbol {
|
||||||
|
if len(parseErrors) > 0 {
|
||||||
|
fmt.Printf("ParseDylibSymbols:Some libraries could not be parsed: %v\n", parseErrors)
|
||||||
|
}
|
||||||
|
fmt.Println("ParseDylibSymbols:", len(symbols), "symbols")
|
||||||
|
}
|
||||||
|
return symbols, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("no symbols found in any dylib. Errors: %v", parseErrors)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getSysLibPaths() []string {
|
||||||
|
var paths []string
|
||||||
|
if runtime.GOOS == "linux" {
|
||||||
|
if debugSymbol {
|
||||||
|
fmt.Println("getSysLibPaths:find sys lib path from linux")
|
||||||
|
}
|
||||||
|
paths = []string{
|
||||||
|
"/usr/lib",
|
||||||
|
"/usr/local/lib",
|
||||||
|
}
|
||||||
|
paths = append(paths, getPath("/etc/ld.so.conf")...)
|
||||||
|
if debugSymbol && len(paths) == 0 {
|
||||||
|
fmt.Println("getSysLibPaths:/etc/ld.so.conf havent find any path")
|
||||||
|
}
|
||||||
|
confd := "/etc/ld.so.conf.d"
|
||||||
|
dir, err := os.Stat(confd)
|
||||||
|
if err != nil || !dir.IsDir() {
|
||||||
|
if debugSymbol {
|
||||||
|
fmt.Println("getSysLibPaths:/etc/ld.so.conf.d not found or not dir")
|
||||||
|
}
|
||||||
|
return paths
|
||||||
|
}
|
||||||
|
// todo(zzy) : wait llgo os.ReadDir support
|
||||||
|
// files, err := os.ReadDir(confd)
|
||||||
|
// if err == nil {
|
||||||
|
// for _, file := range files {
|
||||||
|
// filepath := filepath.Join(confd, file.Name())
|
||||||
|
// paths = append(paths, getPath(filepath)...)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
return paths
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPath(file string) []string {
|
||||||
|
if debugSymbol {
|
||||||
|
fmt.Println("getPath:from", file)
|
||||||
|
}
|
||||||
|
var paths []string
|
||||||
|
content, err := os.ReadFile(file)
|
||||||
|
if err != nil {
|
||||||
|
return paths
|
||||||
|
}
|
||||||
|
lines := strings.Split(string(content), "\n")
|
||||||
|
for _, line := range lines {
|
||||||
|
line = strings.TrimSpace(line)
|
||||||
|
if line != "" && !strings.HasPrefix(line, "#") {
|
||||||
|
if file, err := os.Stat(line); err == nil && file.IsDir() {
|
||||||
|
paths = append(paths, line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return paths
|
||||||
|
}
|
||||||
|
|
||||||
|
// finds the intersection of symbols from the dynamic library's symbol table and the symbols parsed from header files.
|
||||||
|
// It returns a list of symbols that can be externally linked.
|
||||||
|
func GetCommonSymbols(dylibSymbols []*nm.Symbol, headerSymbols map[string]*parse.SymbolInfo) []*types.SymbolInfo {
|
||||||
|
var commonSymbols []*types.SymbolInfo
|
||||||
|
for _, dylibSym := range dylibSymbols {
|
||||||
|
symName := dylibSym.Name
|
||||||
|
if runtime.GOOS == "darwin" {
|
||||||
|
symName = strings.TrimPrefix(symName, "_")
|
||||||
|
}
|
||||||
|
if symInfo, ok := headerSymbols[symName]; ok {
|
||||||
|
symbolInfo := &types.SymbolInfo{
|
||||||
|
Mangle: symName,
|
||||||
|
CPP: symInfo.ProtoName,
|
||||||
|
Go: symInfo.GoName,
|
||||||
|
}
|
||||||
|
commonSymbols = append(commonSymbols, symbolInfo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return commonSymbols
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReadExistingSymbolTable(fileName string) (map[string]types.SymbolInfo, bool) {
|
||||||
|
if _, err := os.Stat(fileName); err != nil {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := os.ReadFile(fileName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
parsedJSON := cjson.ParseBytes(data)
|
||||||
|
if parsedJSON == nil {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
existingSymbols := make(map[string]types.SymbolInfo)
|
||||||
|
arraySize := parsedJSON.GetArraySize()
|
||||||
|
|
||||||
|
for i := 0; i < int(arraySize); i++ {
|
||||||
|
item := parsedJSON.GetArrayItem(c.Int(i))
|
||||||
|
symbol := types.SymbolInfo{
|
||||||
|
Mangle: config.GetStringItem(item, "mangle", ""),
|
||||||
|
CPP: config.GetStringItem(item, "c++", ""),
|
||||||
|
Go: config.GetStringItem(item, "go", ""),
|
||||||
|
}
|
||||||
|
existingSymbols[symbol.Mangle] = symbol
|
||||||
|
}
|
||||||
|
|
||||||
|
return existingSymbols, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func GenSymbolTableData(commonSymbols []*types.SymbolInfo, existingSymbols map[string]types.SymbolInfo) ([]byte, error) {
|
||||||
|
if len(existingSymbols) > 0 {
|
||||||
|
if debugSymbol {
|
||||||
|
fmt.Println("GenSymbolTableData:generate symbol table with exist symbol table")
|
||||||
|
}
|
||||||
|
for i := range commonSymbols {
|
||||||
|
if existingSymbol, exists := existingSymbols[commonSymbols[i].Mangle]; exists && commonSymbols[i].Go != existingSymbol.Go {
|
||||||
|
if debugSymbol {
|
||||||
|
fmt.Println("symbol", commonSymbols[i].Mangle, "already exist, use exist symbol", existingSymbol.Go)
|
||||||
|
}
|
||||||
|
commonSymbols[i].Go = existingSymbol.Go
|
||||||
|
} else {
|
||||||
|
if debugSymbol {
|
||||||
|
fmt.Println("new symbol", commonSymbols[i].Mangle, "-", commonSymbols[i].CPP, "-", commonSymbols[i].Go)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if debugSymbol {
|
||||||
|
fmt.Println("GenSymbolTableData:generate symbol table without symbol table")
|
||||||
|
for _, symbol := range commonSymbols {
|
||||||
|
fmt.Println("new symbol", symbol.Mangle, "-", symbol.CPP, "-", symbol.Go)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
root := cjson.Array()
|
||||||
|
defer root.Delete()
|
||||||
|
|
||||||
|
for _, symbol := range commonSymbols {
|
||||||
|
item := cjson.Object()
|
||||||
|
item.SetItem(c.Str("mangle"), cjson.String(c.AllocaCStr(symbol.Mangle)))
|
||||||
|
item.SetItem(c.Str("c++"), cjson.String(c.AllocaCStr(symbol.CPP)))
|
||||||
|
item.SetItem(c.Str("go"), cjson.String(c.AllocaCStr(symbol.Go)))
|
||||||
|
root.AddItem(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
cStr := root.Print()
|
||||||
|
if cStr == nil {
|
||||||
|
return nil, errors.New("symbol table is empty")
|
||||||
|
}
|
||||||
|
defer c.Free(unsafe.Pointer(cStr))
|
||||||
|
result := []byte(c.GoString(cStr))
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GenerateAndUpdateSymbolTable(symbols []*nm.Symbol, headerInfos map[string]*parse.SymbolInfo, symbFile string) ([]byte, error) {
|
||||||
|
commonSymbols := GetCommonSymbols(symbols, headerInfos)
|
||||||
|
if debugSymbol {
|
||||||
|
fmt.Println("GenerateAndUpdateSymbolTable:", len(commonSymbols), "common symbols")
|
||||||
|
}
|
||||||
|
|
||||||
|
existSymbols, exist := ReadExistingSymbolTable(symbFile)
|
||||||
|
if exist && debugSymbol {
|
||||||
|
fmt.Println("GenerateAndUpdateSymbolTable:current path have exist symbol table", symbFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
symbolData, err := GenSymbolTableData(commonSymbols, existSymbols)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return symbolData, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// For mutiple os test,the nm output's symbol name is different.
|
||||||
|
func AddSymbolPrefixUnder(name string, isCpp bool) string {
|
||||||
|
prefix := ""
|
||||||
|
if runtime.GOOS == "darwin" {
|
||||||
|
prefix = prefix + "_"
|
||||||
|
}
|
||||||
|
if isCpp {
|
||||||
|
prefix = prefix + "_"
|
||||||
|
}
|
||||||
|
return prefix + name
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user