From 75e282c2ac57bfa67f0d25dfc5a3a65bd2ab90ff Mon Sep 17 00:00:00 2001 From: luoliwoshang <2643523683@qq.com> Date: Fri, 18 Oct 2024 23:35:52 +0800 Subject: [PATCH 01/15] llcppsymg & llcppsigfetch parse args --- .../llcppsymg/_cmptest/args_test/args.go | 63 +++++++++++++++++++ .../llcppsymg/_cmptest/args_test/llgo.expect | 54 ++++++++++++++++ chore/_xtool/llcppsymg/args/args.go | 51 +++++++++++++++ 3 files changed, 168 insertions(+) create mode 100644 chore/_xtool/llcppsymg/_cmptest/args_test/args.go create mode 100644 chore/_xtool/llcppsymg/_cmptest/args_test/llgo.expect create mode 100644 chore/_xtool/llcppsymg/args/args.go diff --git a/chore/_xtool/llcppsymg/_cmptest/args_test/args.go b/chore/_xtool/llcppsymg/_cmptest/args_test/args.go new file mode 100644 index 00000000..76dcf7a7 --- /dev/null +++ b/chore/_xtool/llcppsymg/_cmptest/args_test/args.go @@ -0,0 +1,63 @@ +package main + +import ( + "fmt" + + "github.com/goplus/llgo/chore/_xtool/llcppsymg/args" +) + +func main() { + TestParseArgs() +} + +func TestParseArgs() { + fmt.Println("=== Test ParseArgs ===") + + swflags := map[string]bool{ + "--extract": true, + } + + testCases := []struct { + name string + input []string + }{ + { + name: "Basic flags", + input: []string{"-h", "-v", "-"}, + }, + { + name: "Config file", + input: []string{"lua.llcppg.cfg"}, + }, + { + name: "Extract with multiple args", + input: []string{"--extract", "file1.h", "file2.h", "-v"}, + }, + { + name: "Non-skippable flags", + input: []string{"--extract", "file1.h", "file2.h", "-out=true", "-cpp=true", "-v"}, + }, + { + name: "Mixed flags", + input: []string{"-v", "--extract", "file.h", "-out=true", "config.json"}, + }, + { + name: "Empty input", + input: []string{}, + }, + } + + for _, tc := range testCases { + fmt.Printf("Test case: %s\n", tc.name) + fmt.Printf("Input: %v\n", tc.input) + + result, filteredArgs := args.ParseArgs(tc.input, swflags) + + fmt.Printf("Help: %v\n", result.Help) + fmt.Printf("Verbose: %v\n", result.Verbose) + fmt.Printf("UseStdin: %v\n", result.UseStdin) + fmt.Printf("CfgFile: %s\n", result.CfgFile) + fmt.Printf("FilteredArgs: %v\n", filteredArgs) + fmt.Println() + } +} diff --git a/chore/_xtool/llcppsymg/_cmptest/args_test/llgo.expect b/chore/_xtool/llcppsymg/_cmptest/args_test/llgo.expect new file mode 100644 index 00000000..2f1e9416 --- /dev/null +++ b/chore/_xtool/llcppsymg/_cmptest/args_test/llgo.expect @@ -0,0 +1,54 @@ +#stdout +=== Test ParseArgs === +Test case: Basic flags +Input: [-h -v -] +Help: true +Verbose: true +UseStdin: true +CfgFile: llcppg.cfg +FilteredArgs: [] + +Test case: Config file +Input: [lua.llcppg.cfg] +Help: false +Verbose: false +UseStdin: false +CfgFile: lua.llcppg.cfg +FilteredArgs: [] + +Test case: Extract with multiple args +Input: [--extract file1.h file2.h -v] +Help: false +Verbose: true +UseStdin: false +CfgFile: llcppg.cfg +FilteredArgs: [--extract file1.h file2.h] + +Test case: Non-skippable flags +Input: [--extract file1.h file2.h -out=true -cpp=true -v] +Help: false +Verbose: true +UseStdin: false +CfgFile: llcppg.cfg +FilteredArgs: [--extract file1.h file2.h -out=true -cpp=true] + +Test case: Mixed flags +Input: [-v --extract file.h -out=true config.json] +Help: false +Verbose: true +UseStdin: false +CfgFile: config.json +FilteredArgs: [--extract file.h -out=true] + +Test case: Empty input +Input: [] +Help: false +Verbose: false +UseStdin: false +CfgFile: llcppg.cfg +FilteredArgs: [] + + +#stderr + +#exit 0 diff --git a/chore/_xtool/llcppsymg/args/args.go b/chore/_xtool/llcppsymg/args/args.go new file mode 100644 index 00000000..76d1eaa1 --- /dev/null +++ b/chore/_xtool/llcppsymg/args/args.go @@ -0,0 +1,51 @@ +package args + +import "strings" + +type Args struct { + Help bool + Verbose bool + UseStdin bool + CfgFile string +} + +func ParseArgs(args []string, swflags map[string]bool) (*Args, []string) { + result := &Args{} + filteredArgs := []string{} + for i := 0; i < len(args); i++ { + arg := args[i] + if strings.HasPrefix(arg, "-") { + switch arg { + case "-h", "--help": + result.Help = true + continue + case "-v": + result.Verbose = true + continue + case "-": + result.UseStdin = true + continue + default: + if hasArg, ok := swflags[arg]; ok { + if hasArg { + filteredArgs = append(filteredArgs, arg) + for i+1 < len(args) && !strings.HasPrefix(args[i+1], "-") { + filteredArgs = append(filteredArgs, args[i+1]) + i++ + } + continue + } + } + filteredArgs = append(filteredArgs, arg) + } + } else if result.CfgFile == "" { + result.CfgFile = arg + } else { + filteredArgs = append(filteredArgs, arg) + } + } + if result.CfgFile == "" { + result.CfgFile = "llcppg.cfg" + } + return result, filteredArgs +} From 29d527bee1733f9778f55ae51d8259a7fde58cf1 Mon Sep 17 00:00:00 2001 From: luoliwoshang <2643523683@qq.com> Date: Tue, 10 Sep 2024 16:16:51 +0800 Subject: [PATCH 02/15] llcppsymg:refine classname fetch --- chore/_xtool/llcppsymg/parse/parse.go | 64 +++++++-------------------- 1 file changed, 16 insertions(+), 48 deletions(-) diff --git a/chore/_xtool/llcppsymg/parse/parse.go b/chore/_xtool/llcppsymg/parse/parse.go index 98923b3a..1b5a11a7 100644 --- a/chore/_xtool/llcppsymg/parse/parse.go +++ b/chore/_xtool/llcppsymg/parse/parse.go @@ -16,12 +16,10 @@ type SymbolInfo struct { } type Context struct { - namespaceName string - className string - prefixes []string - symbolMap map[string]*SymbolInfo - currentFile string - nameCounts map[string]int + prefixes []string + symbolMap map[string]*SymbolInfo + currentFile string + nameCounts map[string]int } func newContext(prefixes []string) *Context { @@ -32,14 +30,6 @@ func newContext(prefixes []string) *Context { } } -func (c *Context) setNamespaceName(name string) { - c.namespaceName = name -} - -func (c *Context) setClassName(name string) { - c.className = name -} - func (c *Context) setCurrentFile(filename string) { c.currentFile = filename } @@ -79,18 +69,19 @@ func (c *Context) toGoName(name string) string { return toCamel(name) } -func (c *Context) genGoName(name string) string { - class := c.toGoName(c.className) - name = c.toGoName(name) +func (p *Context) genGoName(cursor clang.Cursor) string { + funcName := cursor.String() + defer funcName.Dispose() - var baseName string - if class == "" { - baseName = name - } else { - baseName = c.genMethodName(class, name) + name := p.toGoName(c.GoString(funcName.CStr())) + 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, name)) } - return c.addSuffix(baseName) + return p.addSuffix(name) } func (c *Context) genMethodName(class, name string) string { @@ -132,19 +123,16 @@ func (c *Context) addSuffix(name string) string { var context = newContext([]string{}) func collectFuncInfo(cursor clang.Cursor) { - cursorStr := cursor.String() 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 cursorStr.Dispose() context.symbolMap[symbolName] = &SymbolInfo{ - GoName: context.genGoName(name), + GoName: context.genGoName(cursor), ProtoName: context.genProtoName(cursor), } } @@ -152,30 +140,11 @@ func collectFuncInfo(cursor clang.Cursor) { func visit(cursor, parent clang.Cursor, clientData c.Pointer) clang.ChildVisitResult { switch cursor.Kind { case clang.CursorNamespace, clang.CursorClassDecl: - nameStr := cursor.String() - 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: loc := cursor.Location() var file clang.File - var line, column c.Uint - - loc.SpellingLocation(&file, &line, &column, nil) + loc.SpellingLocation(&file, nil, nil, nil) filename := file.FileName() defer filename.Dispose() @@ -186,7 +155,6 @@ func visit(cursor, parent clang.Cursor, clientData c.Pointer) clang.ChildVisitRe collectFuncInfo(cursor) } } - return clang.ChildVisit_Continue } From a83f7a822e3b4ad79824966c12fc5228d693fb6a Mon Sep 17 00:00:00 2001 From: luoliwoshang <2643523683@qq.com> Date: Tue, 24 Sep 2024 19:17:36 +0800 Subject: [PATCH 03/15] llcppsymg:refine logic & parse symbol test --- .../_xtool/llcppsymg/clangutils/clangutils.go | 13 +- chore/_xtool/llcppsymg/llcppsymg.go | 3 +- chore/_xtool/llcppsymg/parse/parse.go | 135 +++++++------- .../llcppsymg/parse/parse_test/llgo.expect | 44 +++++ .../llcppsymg/parse/parse_test/parse.go | 165 ++++++++++++++++++ 5 files changed, 292 insertions(+), 68 deletions(-) create mode 100644 chore/_xtool/llcppsymg/parse/parse_test/llgo.expect create mode 100644 chore/_xtool/llcppsymg/parse/parse_test/parse.go diff --git a/chore/_xtool/llcppsymg/clangutils/clangutils.go b/chore/_xtool/llcppsymg/clangutils/clangutils.go index 95681d25..79770006 100644 --- a/chore/_xtool/llcppsymg/clangutils/clangutils.go +++ b/chore/_xtool/llcppsymg/clangutils/clangutils.go @@ -16,6 +16,10 @@ type Config struct { 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) { // default use the c/c++ standard of clang; c:gnu17 c++:gnu++17 // https://clang.llvm.org/docs/CommandGuide/clang.html @@ -42,7 +46,7 @@ func CreateTranslationUnit(config *Config) (*clang.Index, *clang.TranslationUnit if config.Temp { content := c.AllocaCStr(config.File) tempFile := &clang.UnsavedFile{ - Filename: c.Str("temp.h"), + Filename: c.Str(TEMP_FILE), Contents: content, Length: c.Ulong(c.Strlen(content)), } @@ -83,3 +87,10 @@ func BuildScopingParts(cursor clang.Cursor) []string { } 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)) +} diff --git a/chore/_xtool/llcppsymg/llcppsymg.go b/chore/_xtool/llcppsymg/llcppsymg.go index e9e0dc78..67cf03a1 100644 --- a/chore/_xtool/llcppsymg/llcppsymg.go +++ b/chore/_xtool/llcppsymg/llcppsymg.go @@ -47,7 +47,6 @@ func main() { data, err = os.ReadFile(cfgFile) } check(err) - conf, err := config.GetConf(data) check(err) defer conf.Delete() @@ -60,7 +59,7 @@ func main() { check(err) filepaths := genHeaderFilePath(conf.CFlags, conf.Include) - headerInfos, err := parse.ParseHeaderFile(filepaths, conf.TrimPrefixes, conf.Cplusplus) + headerInfos, err := parse.ParseHeaderFile(filepaths, conf.TrimPrefixes, conf.Cplusplus, false) check(err) symbolInfo := getCommonSymbols(symbols, headerInfos, conf.TrimPrefixes) diff --git a/chore/_xtool/llcppsymg/parse/parse.go b/chore/_xtool/llcppsymg/parse/parse.go index 1b5a11a7..292a8ff7 100644 --- a/chore/_xtool/llcppsymg/parse/parse.go +++ b/chore/_xtool/llcppsymg/parse/parse.go @@ -15,27 +15,27 @@ type SymbolInfo struct { ProtoName string } -type Context struct { - prefixes []string - symbolMap map[string]*SymbolInfo - currentFile string - nameCounts map[string]int +type SymbolProcessor struct { + Prefixes []string + SymbolMap map[string]*SymbolInfo + CurrentFile string + NameCounts map[string]int } -func newContext(prefixes []string) *Context { - return &Context{ - prefixes: prefixes, - symbolMap: make(map[string]*SymbolInfo), - nameCounts: make(map[string]int), +func NewSymbolProcessor(Prefixes []string) *SymbolProcessor { + return &SymbolProcessor{ + Prefixes: Prefixes, + SymbolMap: make(map[string]*SymbolInfo), + NameCounts: make(map[string]int), } } -func (c *Context) setCurrentFile(filename string) { - c.currentFile = filename +func (p *SymbolProcessor) setCurrentFile(filename string) { + p.CurrentFile = filename } -func (c *Context) removePrefix(str string) string { - for _, prefix := range c.prefixes { +func (p *SymbolProcessor) TrimPrefixes(str string) string { + for _, prefix := range p.Prefixes { if strings.HasPrefix(str, prefix) { return strings.TrimPrefix(str, prefix) } @@ -47,10 +47,10 @@ func toTitle(s string) string { if s == "" { 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 == "" { return "" } @@ -64,38 +64,44 @@ func toCamel(originName string) string { // 1. remove prefix from config // 2. convert to camel case -func (c *Context) toGoName(name string) string { - name = c.removePrefix(name) - return toCamel(name) +func (p *SymbolProcessor) ToGoName(name string) string { + return toUpperCamelCase(p.TrimPrefixes(name)) } -func (p *Context) genGoName(cursor clang.Cursor) string { - funcName := cursor.String() - defer funcName.Dispose() - - name := p.toGoName(c.GoString(funcName.CStr())) - 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, name)) - } - - return p.addSuffix(name) -} - -func (c *Context) genMethodName(class, name string) string { +func (p *SymbolProcessor) GenMethodName(class, name string, isDestructor bool) string { prefix := "(*" + class + ")." + if isDestructor { + return prefix + "Dispose" + } if class == name { return prefix + "Init" } - if name == "~"+class { - return prefix + "Dispose" - } 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() defer displayName.Dispose() @@ -111,36 +117,32 @@ func (p *Context) genProtoName(cursor clang.Cursor) string { return builder.String() } -func (c *Context) addSuffix(name string) string { - c.nameCounts[name]++ - count := c.nameCounts[name] - if count > 1 { +func (p *SymbolProcessor) AddSuffix(name string) string { + p.NameCounts[name]++ + if count := p.NameCounts[name]; count > 1 { return name + "__" + strconv.Itoa(count-1) } return name } -var context = newContext([]string{}) - -func collectFuncInfo(cursor clang.Cursor) { +func (p *SymbolProcessor) collectFuncInfo(cursor clang.Cursor) { symbol := cursor.Mangling() + defer symbol.Dispose() symbolName := c.GoString(symbol.CStr()) if len(symbolName) >= 1 && symbolName[0] == '_' { symbolName = symbolName[1:] } - defer symbol.Dispose() - - context.symbolMap[symbolName] = &SymbolInfo{ - GoName: context.genGoName(cursor), - ProtoName: context.genProtoName(cursor), + 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 { case clang.CursorNamespace, clang.CursorClassDecl: - clang.VisitChildren(cursor, visit, nil) + clangutils.VisitChildren(cursor, p.visitTop) case clang.CursorCXXMethod, clang.CursorFunctionDecl, clang.CursorConstructor, clang.CursorDestructor: loc := cursor.Location() var file clang.File @@ -148,35 +150,38 @@ func visit(cursor, parent clang.Cursor, clientData c.Pointer) clang.ChildVisitRe filename := file.FileName() 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 if isCurrentFile && (cursor.Kind == clang.CursorFunctionDecl || isPublicMethod) { - collectFuncInfo(cursor) + p.collectFuncInfo(cursor) } } return clang.ChildVisit_Continue } -func ParseHeaderFile(filepaths []string, prefixes []string, isCpp bool) (map[string]*SymbolInfo, error) { - context = newContext(prefixes) +func ParseHeaderFile(files []string, Prefixes []string, isCpp bool, isTemp bool) (map[string]*SymbolInfo, error) { + processer := NewSymbolProcessor(Prefixes) index := clang.CreateIndex(0, 0) - for _, filename := range filepaths { + for _, file := range files { _, unit, err := clangutils.CreateTranslationUnit(&clangutils.Config{ - File: filename, - Temp: false, + File: file, + Temp: isTemp, IsCpp: isCpp, Index: index, }) 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() - context.setCurrentFile(filename) - clang.VisitChildren(cursor, visit, nil) + if isTemp { + processer.setCurrentFile(clangutils.TEMP_FILE) + } else { + processer.setCurrentFile(file) + } + clangutils.VisitChildren(cursor, processer.visitTop) unit.Dispose() } index.Dispose() - return context.symbolMap, nil + return processer.SymbolMap, nil } diff --git a/chore/_xtool/llcppsymg/parse/parse_test/llgo.expect b/chore/_xtool/llcppsymg/parse/parse_test/llgo.expect new file mode 100644 index 00000000..819ff4b8 --- /dev/null +++ b/chore/_xtool/llcppsymg/parse/parse_test/llgo.expect @@ -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 diff --git a/chore/_xtool/llcppsymg/parse/parse_test/parse.go b/chore/_xtool/llcppsymg/parse/parse_test/parse.go new file mode 100644 index 00000000..4a827b45 --- /dev/null +++ b/chore/_xtool/llcppsymg/parse/parse_test/parse.go @@ -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() + } +} From ffa823f748b17a7b8e19772af76106d89b95b6ad Mon Sep 17 00:00:00 2001 From: luoliwoshang <2643523683@qq.com> Date: Wed, 25 Sep 2024 11:56:43 +0800 Subject: [PATCH 04/15] llcppsymg:gendylib path test llcppsymg:headerpath test --- chore/_xtool/llcppsymg/dylib/dylib.go | 66 +++++++++++ .../llcppsymg/dylib/dylib_test/dylib.go | 51 +++++++++ .../llcppsymg/dylib/dylib_test/llgo.expect | 22 ++++ chore/_xtool/llcppsymg/header/header.go | 23 ++++ .../llcppsymg/header/header_test/header.go | 104 ++++++++++++++++++ .../llcppsymg/header/header_test/llgo.expect | 23 ++++ chore/_xtool/llcppsymg/llcppsymg.go | 58 +--------- 7 files changed, 294 insertions(+), 53 deletions(-) create mode 100644 chore/_xtool/llcppsymg/dylib/dylib.go create mode 100644 chore/_xtool/llcppsymg/dylib/dylib_test/dylib.go create mode 100644 chore/_xtool/llcppsymg/dylib/dylib_test/llgo.expect create mode 100644 chore/_xtool/llcppsymg/header/header.go create mode 100644 chore/_xtool/llcppsymg/header/header_test/header.go create mode 100644 chore/_xtool/llcppsymg/header/header_test/llgo.expect diff --git a/chore/_xtool/llcppsymg/dylib/dylib.go b/chore/_xtool/llcppsymg/dylib/dylib.go new file mode 100644 index 00000000..73df5f4e --- /dev/null +++ b/chore/_xtool/llcppsymg/dylib/dylib.go @@ -0,0 +1,66 @@ +package dylib + +import ( + "errors" + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/goplus/llgo/xtool/nm" +) + +func ParseDylibSymbols(lib string) ([]*nm.Symbol, error) { + fmt.Printf("parse dylib symbols from config lib:%s\n", lib) + dylibPaths, err := GenDylibPaths(lib) + if err != nil { + return nil, errors.New("failed to generate dylib path") + } + + var symbols []*nm.Symbol + for _, dylibPath := range dylibPaths { + if _, err := os.Stat(dylibPath); err != nil { + fmt.Printf("Failed to access dylib error: %s\n", err) + continue + } + + files, err := nm.New("").List(dylibPath) + if err != nil { + fmt.Printf("Failed to list symbols in dylib: %s, error: %s\n", dylibPath, err) + continue + } + + for _, file := range files { + symbols = append(symbols, file.Symbols...) + } + } + + if len(symbols) == 0 { + return nil, errors.New("no symbols found in any dylib") + } + + return symbols, nil +} + +func GenDylibPaths(lib string) ([]string, error) { + parts := strings.Fields(lib) + var libPath, libName string + var dylibPaths []string + + for _, part := range parts { + if strings.HasPrefix(part, "-L") { + libPath = part[2:] + } else if strings.HasPrefix(part, "-l") { + libName = part[2:] + if libPath != "" && libName != "" { + dylibPaths = append(dylibPaths, filepath.Join(libPath, "lib"+libName+".dylib")) + } + } + } + + if len(dylibPaths) == 0 { + return nil, fmt.Errorf("failed to parse pkg-config output: %s", lib) + } + + return dylibPaths, nil +} diff --git a/chore/_xtool/llcppsymg/dylib/dylib_test/dylib.go b/chore/_xtool/llcppsymg/dylib/dylib_test/dylib.go new file mode 100644 index 00000000..9999a165 --- /dev/null +++ b/chore/_xtool/llcppsymg/dylib/dylib_test/dylib.go @@ -0,0 +1,51 @@ +package main + +import ( + "fmt" + + "github.com/goplus/llgo/chore/_xtool/llcppsymg/dylib" +) + +func TestGenDylibPaths() { + fmt.Println("=== Test GenDylibPaths ===") + + 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: "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) + + result, err := dylib.GenDylibPaths(tc.input) + + if err != nil { + fmt.Printf("Error: %v\n", err) + } else { + fmt.Printf("Output: %v\n", result) + } + fmt.Println() + } +} + +func main() { + TestGenDylibPaths() +} diff --git a/chore/_xtool/llcppsymg/dylib/dylib_test/llgo.expect b/chore/_xtool/llcppsymg/dylib/dylib_test/llgo.expect new file mode 100644 index 00000000..3feb86f3 --- /dev/null +++ b/chore/_xtool/llcppsymg/dylib/dylib_test/llgo.expect @@ -0,0 +1,22 @@ +#stdout +=== Test GenDylibPaths === +Test case: Lua library +Input: -L/opt/homebrew/lib -llua -lm +Output: [/opt/homebrew/lib/liblua.dylib /opt/homebrew/lib/libm.dylib] + +Test case: SQLite library +Input: -L/opt/homebrew/opt/sqlite/lib -lsqlite3 +Output: [/opt/homebrew/opt/sqlite/lib/libsqlite3.dylib] + +Test case: INIReader library +Input: -L/opt/homebrew/Cellar/inih/58/lib -lINIReader +Output: [/opt/homebrew/Cellar/inih/58/lib/libINIReader.dylib] + +Test case: No valid library +Input: -L/opt/homebrew/lib +Error: failed to parse pkg-config output: -L/opt/homebrew/lib + + +#stderr + +#exit 0 diff --git a/chore/_xtool/llcppsymg/header/header.go b/chore/_xtool/llcppsymg/header/header.go new file mode 100644 index 00000000..392b0628 --- /dev/null +++ b/chore/_xtool/llcppsymg/header/header.go @@ -0,0 +1,23 @@ +package header + +import ( + "fmt" + "path/filepath" + "strings" +) + +func GenHeaderFilePath(cflags string, files []string) ([]string, error) { + fmt.Printf("get filepath from config cflags%s & include:%v\n", cflags, files) + prefixPath := strings.TrimPrefix(cflags, "-I") + var includePaths []string + for _, file := range files { + if file == "" { + continue + } + includePaths = append(includePaths, filepath.Join(prefixPath, "/"+file)) + } + if len(includePaths) == 0 { + return nil, fmt.Errorf("no valid header files") + } + return includePaths, nil +} diff --git a/chore/_xtool/llcppsymg/header/header_test/header.go b/chore/_xtool/llcppsymg/header/header_test/header.go new file mode 100644 index 00000000..9378aae7 --- /dev/null +++ b/chore/_xtool/llcppsymg/header/header_test/header.go @@ -0,0 +1,104 @@ +package main + +import ( + "fmt" + "os" + "path/filepath" + "strings" +) + +func GenHeaderFilePath(cflags string, files []string) ([]string, error) { + prefixPath := strings.TrimPrefix(cflags, "-I") + + var validPaths []string + var errs []string + + for _, file := range files { + if file == "" { + continue + } + fullPath := filepath.Join(prefixPath, file) + if f, err := os.Open(fullPath); err != nil { + if os.IsNotExist(err) { + errs = append(errs, fmt.Sprintf("file not found: %s", file)) + } else { + errs = append(errs, fmt.Sprintf("error accessing file %s: %v", file, err)) + } + } else { + f.Close() + validPaths = append(validPaths, fullPath) + } + } + + if len(validPaths) == 0 && len(errs) == 0 { + return nil, fmt.Errorf("no valid header files") + } + + if len(errs) > 0 { + return validPaths, fmt.Errorf("some files not found or inaccessible: %v", errs) + } + + return validPaths, nil +} + +func TestGenHeaderFilePath() { + fmt.Println("=== Test GenHeaderFilePath ===") + + tempDir := os.TempDir() + tempFile1 := filepath.Join(tempDir, "test1.h") + tempFile2 := filepath.Join(tempDir, "test2.h") + os.Create(tempFile1) + os.Create(tempFile2) + defer os.Remove(tempFile1) + defer os.Remove(tempFile2) + + 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: "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) + + result, err := GenHeaderFilePath(tc.cflags, tc.files) + + if err != nil { + fmt.Printf("Error: %v\n", err) + } + 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() + } +} + +func main() { + TestGenHeaderFilePath() +} diff --git a/chore/_xtool/llcppsymg/header/header_test/llgo.expect b/chore/_xtool/llcppsymg/header/header_test/llgo.expect new file mode 100644 index 00000000..cff9314e --- /dev/null +++ b/chore/_xtool/llcppsymg/header/header_test/llgo.expect @@ -0,0 +1,23 @@ +#stdout +=== 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] +Error: some files not found or inaccessible: [file not found: nonexistent.h] +Output: [test1.h] + +Test case: No existing files +Input files: [nonexistent1.h nonexistent2.h] +Error: some files not found or inaccessible: [file not found: nonexistent1.h file not found: nonexistent2.h] + +Test case: Empty file list +Input files: [] +Error: no valid header files + + +#stderr + +#exit 0 diff --git a/chore/_xtool/llcppsymg/llcppsymg.go b/chore/_xtool/llcppsymg/llcppsymg.go index 67cf03a1..3a212cae 100644 --- a/chore/_xtool/llcppsymg/llcppsymg.go +++ b/chore/_xtool/llcppsymg/llcppsymg.go @@ -21,13 +21,14 @@ import ( "fmt" "io" "os" - "path/filepath" "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/dylib" + "github.com/goplus/llgo/chore/_xtool/llcppsymg/header" "github.com/goplus/llgo/chore/_xtool/llcppsymg/parse" "github.com/goplus/llgo/chore/llcppg/types" "github.com/goplus/llgo/xtool/nm" @@ -54,11 +55,11 @@ func main() { if err != nil { fmt.Fprintln(os.Stderr, "Failed to parse config file:", cfgFile) } - symbols, err := parseDylibSymbols(conf.Libs) - + symbols, err := dylib.ParseDylibSymbols(conf.Libs) check(err) - filepaths := genHeaderFilePath(conf.CFlags, conf.Include) + filepaths, err := header.GenHeaderFilePath(conf.CFlags, conf.Include) + check(err) headerInfos, err := parse.ParseHeaderFile(filepaths, conf.TrimPrefixes, conf.Cplusplus, false) check(err) @@ -74,55 +75,6 @@ func check(err error) { } } -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 { From 174fdd40da8a34102f3a0fbf318ba42698694302 Mon Sep 17 00:00:00 2001 From: luoliwoshang <2643523683@qq.com> Date: Wed, 25 Sep 2024 13:03:35 +0800 Subject: [PATCH 05/15] llcppsymg:refine dylib parse --- chore/_xtool/llcppsymg/dylib/dylib.go | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/chore/_xtool/llcppsymg/dylib/dylib.go b/chore/_xtool/llcppsymg/dylib/dylib.go index 73df5f4e..41752e4f 100644 --- a/chore/_xtool/llcppsymg/dylib/dylib.go +++ b/chore/_xtool/llcppsymg/dylib/dylib.go @@ -1,7 +1,6 @@ package dylib import ( - "errors" "fmt" "os" "path/filepath" @@ -10,23 +9,32 @@ import ( "github.com/goplus/llgo/xtool/nm" ) +// 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) { fmt.Printf("parse dylib symbols from config lib:%s\n", lib) + dylibPaths, err := GenDylibPaths(lib) if err != nil { - return nil, errors.New("failed to generate dylib path") + fmt.Printf("Warning: failed to generate some dylib paths: %v\n", err) } var symbols []*nm.Symbol + var parseErrors []string + for _, dylibPath := range dylibPaths { if _, err := os.Stat(dylibPath); err != nil { - fmt.Printf("Failed to access dylib error: %s\n", err) + fmt.Printf("Warning: Failed to access dylib %s: %v\n", dylibPath, err) continue } files, err := nm.New("").List(dylibPath) if err != nil { - fmt.Printf("Failed to list symbols in dylib: %s, error: %s\n", dylibPath, err) + parseErrors = append(parseErrors, fmt.Sprintf("Failed to list symbols in dylib %s: %v", dylibPath, err)) continue } @@ -35,11 +43,14 @@ func ParseDylibSymbols(lib string) ([]*nm.Symbol, error) { } } - if len(symbols) == 0 { - return nil, errors.New("no symbols found in any dylib") + if len(symbols) > 0 { + if len(parseErrors) > 0 { + fmt.Printf("Warning: Some libraries could not be parsed: %v\n", parseErrors) + } + return symbols, nil } - return symbols, nil + return nil, fmt.Errorf("no symbols found in any dylib. Errors: %v", parseErrors) } func GenDylibPaths(lib string) ([]string, error) { From 944133de6e0d4edc05b5af07c5993e5630ee1bbb Mon Sep 17 00:00:00 2001 From: luoliwoshang <2643523683@qq.com> Date: Wed, 25 Sep 2024 14:59:02 +0800 Subject: [PATCH 06/15] llcppsymg:symbol generate test llcppsymg:symbo test llcppsymg:exist symb file test llcppsymg:GenSymbolTabledata llcppsymg:GenSymbolTableData test llcppsymg:full symg operation test --- .../{dylib => _cmptest}/dylib_test/dylib.go | 0 .../dylib_test/llgo.expect | 0 .../header_test/header.go | 39 +---- .../header_test/llgo.expect | 0 .../parse_test/llgo.expect | 0 .../{parse => _cmptest}/parse_test/parse.go | 0 .../_cmptest/symbol_test/llgo.expect | 46 ++++++ .../llcppsymg/_cmptest/symbol_test/symbol.go | 152 ++++++++++++++++++ .../llcppsymg/_cmptest/symg_test/llgo.expect | 45 ++++++ .../llcppsymg/_cmptest/symg_test/symg.go | 117 ++++++++++++++ chore/_xtool/llcppsymg/header/header.go | 29 +++- chore/_xtool/llcppsymg/llcppsymg.go | 103 +----------- chore/_xtool/llcppsymg/symbol/symbol.go | 107 ++++++++++++ 13 files changed, 499 insertions(+), 139 deletions(-) rename chore/_xtool/llcppsymg/{dylib => _cmptest}/dylib_test/dylib.go (100%) rename chore/_xtool/llcppsymg/{dylib => _cmptest}/dylib_test/llgo.expect (100%) rename chore/_xtool/llcppsymg/{header => _cmptest}/header_test/header.go (60%) rename chore/_xtool/llcppsymg/{header => _cmptest}/header_test/llgo.expect (100%) rename chore/_xtool/llcppsymg/{parse => _cmptest}/parse_test/llgo.expect (100%) rename chore/_xtool/llcppsymg/{parse => _cmptest}/parse_test/parse.go (100%) create mode 100644 chore/_xtool/llcppsymg/_cmptest/symbol_test/llgo.expect create mode 100644 chore/_xtool/llcppsymg/_cmptest/symbol_test/symbol.go create mode 100644 chore/_xtool/llcppsymg/_cmptest/symg_test/llgo.expect create mode 100644 chore/_xtool/llcppsymg/_cmptest/symg_test/symg.go create mode 100644 chore/_xtool/llcppsymg/symbol/symbol.go diff --git a/chore/_xtool/llcppsymg/dylib/dylib_test/dylib.go b/chore/_xtool/llcppsymg/_cmptest/dylib_test/dylib.go similarity index 100% rename from chore/_xtool/llcppsymg/dylib/dylib_test/dylib.go rename to chore/_xtool/llcppsymg/_cmptest/dylib_test/dylib.go diff --git a/chore/_xtool/llcppsymg/dylib/dylib_test/llgo.expect b/chore/_xtool/llcppsymg/_cmptest/dylib_test/llgo.expect similarity index 100% rename from chore/_xtool/llcppsymg/dylib/dylib_test/llgo.expect rename to chore/_xtool/llcppsymg/_cmptest/dylib_test/llgo.expect diff --git a/chore/_xtool/llcppsymg/header/header_test/header.go b/chore/_xtool/llcppsymg/_cmptest/header_test/header.go similarity index 60% rename from chore/_xtool/llcppsymg/header/header_test/header.go rename to chore/_xtool/llcppsymg/_cmptest/header_test/header.go index 9378aae7..44611b29 100644 --- a/chore/_xtool/llcppsymg/header/header_test/header.go +++ b/chore/_xtool/llcppsymg/_cmptest/header_test/header.go @@ -4,43 +4,10 @@ import ( "fmt" "os" "path/filepath" - "strings" + + "github.com/goplus/llgo/chore/_xtool/llcppsymg/header" ) -func GenHeaderFilePath(cflags string, files []string) ([]string, error) { - prefixPath := strings.TrimPrefix(cflags, "-I") - - var validPaths []string - var errs []string - - for _, file := range files { - if file == "" { - continue - } - fullPath := filepath.Join(prefixPath, file) - if f, err := os.Open(fullPath); err != nil { - if os.IsNotExist(err) { - errs = append(errs, fmt.Sprintf("file not found: %s", file)) - } else { - errs = append(errs, fmt.Sprintf("error accessing file %s: %v", file, err)) - } - } else { - f.Close() - validPaths = append(validPaths, fullPath) - } - } - - if len(validPaths) == 0 && len(errs) == 0 { - return nil, fmt.Errorf("no valid header files") - } - - if len(errs) > 0 { - return validPaths, fmt.Errorf("some files not found or inaccessible: %v", errs) - } - - return validPaths, nil -} - func TestGenHeaderFilePath() { fmt.Println("=== Test GenHeaderFilePath ===") @@ -83,7 +50,7 @@ func TestGenHeaderFilePath() { fmt.Printf("Test case: %s\n", tc.name) fmt.Printf("Input files: %v\n", tc.files) - result, err := GenHeaderFilePath(tc.cflags, tc.files) + result, err := header.GenHeaderFilePath(tc.cflags, tc.files) if err != nil { fmt.Printf("Error: %v\n", err) diff --git a/chore/_xtool/llcppsymg/header/header_test/llgo.expect b/chore/_xtool/llcppsymg/_cmptest/header_test/llgo.expect similarity index 100% rename from chore/_xtool/llcppsymg/header/header_test/llgo.expect rename to chore/_xtool/llcppsymg/_cmptest/header_test/llgo.expect diff --git a/chore/_xtool/llcppsymg/parse/parse_test/llgo.expect b/chore/_xtool/llcppsymg/_cmptest/parse_test/llgo.expect similarity index 100% rename from chore/_xtool/llcppsymg/parse/parse_test/llgo.expect rename to chore/_xtool/llcppsymg/_cmptest/parse_test/llgo.expect diff --git a/chore/_xtool/llcppsymg/parse/parse_test/parse.go b/chore/_xtool/llcppsymg/_cmptest/parse_test/parse.go similarity index 100% rename from chore/_xtool/llcppsymg/parse/parse_test/parse.go rename to chore/_xtool/llcppsymg/_cmptest/parse_test/parse.go diff --git a/chore/_xtool/llcppsymg/_cmptest/symbol_test/llgo.expect b/chore/_xtool/llcppsymg/_cmptest/symbol_test/llgo.expect new file mode 100644 index 00000000..4a329943 --- /dev/null +++ b/chore/_xtool/llcppsymg/_cmptest/symbol_test/llgo.expect @@ -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 diff --git a/chore/_xtool/llcppsymg/_cmptest/symbol_test/symbol.go b/chore/_xtool/llcppsymg/_cmptest/symbol_test/symbol.go new file mode 100644 index 00000000..81bbe460 --- /dev/null +++ b/chore/_xtool/llcppsymg/_cmptest/symbol_test/symbol.go @@ -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: "_lua_absindex"}, + {Name: "_lua_arith"}, + {Name: "_lua_atpanic"}, + {Name: "_lua_callk"}, + {Name: "_lua_lib_nonexistent"}, + }, + 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: "_ZNK9INIReader12GetInteger64ERKNSt3__112basic_stringIcNS0_11char_traitsIcEENS0_9allocatorIcEEEES8_x"}, + {Name: "_ZNK9INIReader7GetRealERKNSt3__112basic_stringIcNS0_11char_traitsIcEENS0_9allocatorIcEEEES8_d"}, + {Name: "_ZNK9INIReader10ParseErrorEv"}, + }, + 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() +} diff --git a/chore/_xtool/llcppsymg/_cmptest/symg_test/llgo.expect b/chore/_xtool/llcppsymg/_cmptest/symg_test/llgo.expect new file mode 100644 index 00000000..93bf120d --- /dev/null +++ b/chore/_xtool/llcppsymg/_cmptest/symg_test/llgo.expect @@ -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 diff --git a/chore/_xtool/llcppsymg/_cmptest/symg_test/symg.go b/chore/_xtool/llcppsymg/_cmptest/symg_test/symg.go new file mode 100644 index 00000000..11173662 --- /dev/null +++ b/chore/_xtool/llcppsymg/_cmptest/symg_test/symg.go @@ -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: "__ZN9INIReaderC1EPKc"}, + {Name: "__ZN9INIReaderC1EPKcl"}, + {Name: "__ZN9INIReaderD1Ev"}, + {Name: "__ZNK9INIReader10ParseErrorEv"}, + {Name: "__ZNK9INIReader3GetEPKcS1_S1_"}, + }, + 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: "_lua_error"}, + {Name: "_lua_next"}, + {Name: "_lua_concat"}, + {Name: "_lua_stringtonumber"}, + }, + }, + } + + 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()) + } +} diff --git a/chore/_xtool/llcppsymg/header/header.go b/chore/_xtool/llcppsymg/header/header.go index 392b0628..9390ea2c 100644 --- a/chore/_xtool/llcppsymg/header/header.go +++ b/chore/_xtool/llcppsymg/header/header.go @@ -2,22 +2,41 @@ package header import ( "fmt" + "os" "path/filepath" "strings" ) func GenHeaderFilePath(cflags string, files []string) ([]string, error) { - fmt.Printf("get filepath from config cflags%s & include:%v\n", cflags, files) prefixPath := strings.TrimPrefix(cflags, "-I") - var includePaths []string + + var validPaths []string + var errs []string + for _, file := range files { if file == "" { continue } - includePaths = append(includePaths, filepath.Join(prefixPath, "/"+file)) + fullPath := filepath.Join(prefixPath, file) + if f, err := os.Open(fullPath); err != nil { + if os.IsNotExist(err) { + errs = append(errs, fmt.Sprintf("file not found: %s", file)) + } else { + errs = append(errs, fmt.Sprintf("error accessing file %s: %v", file, err)) + } + } else { + f.Close() + validPaths = append(validPaths, fullPath) + } } - if len(includePaths) == 0 { + + if len(validPaths) == 0 && len(errs) == 0 { return nil, fmt.Errorf("no valid header files") } - return includePaths, nil + + if len(errs) > 0 { + return validPaths, fmt.Errorf("some files not found or inaccessible: %v", errs) + } + + return validPaths, nil } diff --git a/chore/_xtool/llcppsymg/llcppsymg.go b/chore/_xtool/llcppsymg/llcppsymg.go index 3a212cae..272efd7a 100644 --- a/chore/_xtool/llcppsymg/llcppsymg.go +++ b/chore/_xtool/llcppsymg/llcppsymg.go @@ -17,25 +17,20 @@ package main import ( - "errors" "fmt" "io" "os" - "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/dylib" "github.com/goplus/llgo/chore/_xtool/llcppsymg/header" "github.com/goplus/llgo/chore/_xtool/llcppsymg/parse" - "github.com/goplus/llgo/chore/llcppg/types" - "github.com/goplus/llgo/xtool/nm" + "github.com/goplus/llgo/chore/_xtool/llcppsymg/symbol" ) func main() { cfgFile := "llcppg.cfg" + symbFile := "llcppg.symb.json" if len(os.Args) > 1 { cfgFile = os.Args[1] } @@ -63,9 +58,10 @@ func main() { headerInfos, err := parse.ParseHeaderFile(filepaths, conf.TrimPrefixes, conf.Cplusplus, false) check(err) - symbolInfo := getCommonSymbols(symbols, headerInfos, conf.TrimPrefixes) + symbolData, err := symbol.GenerateAndUpdateSymbolTable(symbols, headerInfos, symbFile) + check(err) - err = genSymbolTableFile(symbolInfo) + err = os.WriteFile(symbFile, symbolData, 0644) check(err) } @@ -74,92 +70,3 @@ func check(err error) { panic(err) } } - -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 -} diff --git a/chore/_xtool/llcppsymg/symbol/symbol.go b/chore/_xtool/llcppsymg/symbol/symbol.go new file mode 100644 index 00000000..60ab5c6d --- /dev/null +++ b/chore/_xtool/llcppsymg/symbol/symbol.go @@ -0,0 +1,107 @@ +package symbol + +import ( + "errors" + "os" + "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/parse" + "github.com/goplus/llgo/chore/llcppg/types" + "github.com/goplus/llgo/xtool/nm" +) + +// 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 := strings.TrimPrefix(dylibSym.Name, "_") + 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) { + // todo(zzy): len(existingSymbols) !=0 + // https://github.com/goplus/llgo/issues/808 will cause unexpected panic + // https://github.com/goplus/llgo/pull/793 this pr can fix it + for i := range commonSymbols { + if existingSymbol, exists := existingSymbols[commonSymbols[i].Mangle]; exists { + commonSymbols[i].Go = existingSymbol.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) + + existSymbols, _ := ReadExistingSymbolTable(symbFile) + + symbolData, err := GenSymbolTableData(commonSymbols, existSymbols) + if err != nil { + return nil, err + } + + return symbolData, nil +} From ca0492d997bd8d10e4be25f2c2afd6ca60f86107 Mon Sep 17 00:00:00 2001 From: luoliwoshang <2643523683@qq.com> Date: Thu, 26 Sep 2024 11:12:23 +0800 Subject: [PATCH 07/15] llcppsymg:config & clangutil test llcppsymg:clangutil test --- .../_cmptest/clangutils_test/clangutils.go | 119 ++++++++++++++++++ .../_cmptest/clangutils_test/llgo.expect | 23 ++++ .../llcppsymg/_cmptest/config_test/config.go | 63 ++++++++++ .../_cmptest/config_test/llgo.expect | 24 ++++ 4 files changed, 229 insertions(+) create mode 100644 chore/_xtool/llcppsymg/_cmptest/clangutils_test/clangutils.go create mode 100644 chore/_xtool/llcppsymg/_cmptest/clangutils_test/llgo.expect create mode 100644 chore/_xtool/llcppsymg/_cmptest/config_test/config.go create mode 100644 chore/_xtool/llcppsymg/_cmptest/config_test/llgo.expect diff --git a/chore/_xtool/llcppsymg/_cmptest/clangutils_test/clangutils.go b/chore/_xtool/llcppsymg/_cmptest/clangutils_test/clangutils.go new file mode 100644 index 00000000..1dd06781 --- /dev/null +++ b/chore/_xtool/llcppsymg/_cmptest/clangutils_test/clangutils.go @@ -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() + } +} diff --git a/chore/_xtool/llcppsymg/_cmptest/clangutils_test/llgo.expect b/chore/_xtool/llcppsymg/_cmptest/clangutils_test/llgo.expect new file mode 100644 index 00000000..de8589e7 --- /dev/null +++ b/chore/_xtool/llcppsymg/_cmptest/clangutils_test/llgo.expect @@ -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 diff --git a/chore/_xtool/llcppsymg/_cmptest/config_test/config.go b/chore/_xtool/llcppsymg/_cmptest/config_test/config.go new file mode 100644 index 00000000..6ef2e2d1 --- /dev/null +++ b/chore/_xtool/llcppsymg/_cmptest/config_test/config.go @@ -0,0 +1,63 @@ +package main + +import ( + "fmt" + "strings" + + "github.com/goplus/llgo/chore/_xtool/llcppsymg/config" +) + +func main() { + TestGetConf() +} + +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() + } +} diff --git a/chore/_xtool/llcppsymg/_cmptest/config_test/llgo.expect b/chore/_xtool/llcppsymg/_cmptest/config_test/llgo.expect new file mode 100644 index 00000000..acbf814f --- /dev/null +++ b/chore/_xtool/llcppsymg/_cmptest/config_test/llgo.expect @@ -0,0 +1,24 @@ +#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 + + +#stderr + +#exit 0 From 60aa74257f7573ba39011a70d50471abfad92544 Mon Sep 17 00:00:00 2001 From: luoliwoshang <2643523683@qq.com> Date: Tue, 8 Oct 2024 11:17:33 +0800 Subject: [PATCH 08/15] chore:remove folder --- .../llcppsymg/_cmptest/dylib_test/dylib.go | 51 ------------ .../llcppsymg/_cmptest/dylib_test/llgo.expect | 22 ------ .../llcppsymg/_cmptest/header_test/header.go | 71 ----------------- .../_cmptest/header_test/llgo.expect | 23 ------ .../llcppsymg/_cmptest/parse_test/llgo.expect | 18 +++++ .../llcppsymg/_cmptest/parse_test/parse.go | 61 +++++++++++++++ .../_cmptest/symbol_test/llgo.expect | 17 ++++ .../llcppsymg/_cmptest/symbol_test/symbol.go | 41 +++++++++- .../_xtool/llcppsymg/clangutils/clangutils.go | 5 ++ chore/_xtool/llcppsymg/dylib/dylib.go | 77 ------------------- chore/_xtool/llcppsymg/header/header.go | 42 ---------- chore/_xtool/llcppsymg/llcppsymg.go | 6 +- chore/_xtool/llcppsymg/parse/parse.go | 40 +++++++++- chore/_xtool/llcppsymg/symbol/symbol.go | 69 +++++++++++++++++ 14 files changed, 250 insertions(+), 293 deletions(-) delete mode 100644 chore/_xtool/llcppsymg/_cmptest/dylib_test/dylib.go delete mode 100644 chore/_xtool/llcppsymg/_cmptest/dylib_test/llgo.expect delete mode 100644 chore/_xtool/llcppsymg/_cmptest/header_test/header.go delete mode 100644 chore/_xtool/llcppsymg/_cmptest/header_test/llgo.expect delete mode 100644 chore/_xtool/llcppsymg/dylib/dylib.go delete mode 100644 chore/_xtool/llcppsymg/header/header.go diff --git a/chore/_xtool/llcppsymg/_cmptest/dylib_test/dylib.go b/chore/_xtool/llcppsymg/_cmptest/dylib_test/dylib.go deleted file mode 100644 index 9999a165..00000000 --- a/chore/_xtool/llcppsymg/_cmptest/dylib_test/dylib.go +++ /dev/null @@ -1,51 +0,0 @@ -package main - -import ( - "fmt" - - "github.com/goplus/llgo/chore/_xtool/llcppsymg/dylib" -) - -func TestGenDylibPaths() { - fmt.Println("=== Test GenDylibPaths ===") - - 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: "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) - - result, err := dylib.GenDylibPaths(tc.input) - - if err != nil { - fmt.Printf("Error: %v\n", err) - } else { - fmt.Printf("Output: %v\n", result) - } - fmt.Println() - } -} - -func main() { - TestGenDylibPaths() -} diff --git a/chore/_xtool/llcppsymg/_cmptest/dylib_test/llgo.expect b/chore/_xtool/llcppsymg/_cmptest/dylib_test/llgo.expect deleted file mode 100644 index 3feb86f3..00000000 --- a/chore/_xtool/llcppsymg/_cmptest/dylib_test/llgo.expect +++ /dev/null @@ -1,22 +0,0 @@ -#stdout -=== Test GenDylibPaths === -Test case: Lua library -Input: -L/opt/homebrew/lib -llua -lm -Output: [/opt/homebrew/lib/liblua.dylib /opt/homebrew/lib/libm.dylib] - -Test case: SQLite library -Input: -L/opt/homebrew/opt/sqlite/lib -lsqlite3 -Output: [/opt/homebrew/opt/sqlite/lib/libsqlite3.dylib] - -Test case: INIReader library -Input: -L/opt/homebrew/Cellar/inih/58/lib -lINIReader -Output: [/opt/homebrew/Cellar/inih/58/lib/libINIReader.dylib] - -Test case: No valid library -Input: -L/opt/homebrew/lib -Error: failed to parse pkg-config output: -L/opt/homebrew/lib - - -#stderr - -#exit 0 diff --git a/chore/_xtool/llcppsymg/_cmptest/header_test/header.go b/chore/_xtool/llcppsymg/_cmptest/header_test/header.go deleted file mode 100644 index 44611b29..00000000 --- a/chore/_xtool/llcppsymg/_cmptest/header_test/header.go +++ /dev/null @@ -1,71 +0,0 @@ -package main - -import ( - "fmt" - "os" - "path/filepath" - - "github.com/goplus/llgo/chore/_xtool/llcppsymg/header" -) - -func TestGenHeaderFilePath() { - fmt.Println("=== Test GenHeaderFilePath ===") - - tempDir := os.TempDir() - tempFile1 := filepath.Join(tempDir, "test1.h") - tempFile2 := filepath.Join(tempDir, "test2.h") - os.Create(tempFile1) - os.Create(tempFile2) - defer os.Remove(tempFile1) - defer os.Remove(tempFile2) - - 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: "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) - - result, err := header.GenHeaderFilePath(tc.cflags, tc.files) - - if err != nil { - fmt.Printf("Error: %v\n", err) - } - 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() - } -} - -func main() { - TestGenHeaderFilePath() -} diff --git a/chore/_xtool/llcppsymg/_cmptest/header_test/llgo.expect b/chore/_xtool/llcppsymg/_cmptest/header_test/llgo.expect deleted file mode 100644 index cff9314e..00000000 --- a/chore/_xtool/llcppsymg/_cmptest/header_test/llgo.expect +++ /dev/null @@ -1,23 +0,0 @@ -#stdout -=== 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] -Error: some files not found or inaccessible: [file not found: nonexistent.h] -Output: [test1.h] - -Test case: No existing files -Input files: [nonexistent1.h nonexistent2.h] -Error: some files not found or inaccessible: [file not found: nonexistent1.h file not found: nonexistent2.h] - -Test case: Empty file list -Input files: [] -Error: no valid header files - - -#stderr - -#exit 0 diff --git a/chore/_xtool/llcppsymg/_cmptest/parse_test/llgo.expect b/chore/_xtool/llcppsymg/_cmptest/parse_test/llgo.expect index 819ff4b8..55bb70a2 100644 --- a/chore/_xtool/llcppsymg/_cmptest/parse_test/llgo.expect +++ b/chore/_xtool/llcppsymg/_cmptest/parse_test/llgo.expect @@ -1,4 +1,22 @@ #stdout +=== 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] +Error: some files not found or inaccessible: [file not found: nonexistent.h] +Output: [test1.h] + +Test case: No existing files +Input files: [nonexistent1.h nonexistent2.h] +Error: some files not found or inaccessible: [file not found: nonexistent1.h file not found: nonexistent2.h] + +Test case: Empty file list +Input files: [] +Error: no valid header files + === Test NewSymbolProcessor === Before: No prefixes After: Prefixes: [lua_ luaL_] diff --git a/chore/_xtool/llcppsymg/_cmptest/parse_test/parse.go b/chore/_xtool/llcppsymg/_cmptest/parse_test/parse.go index 4a827b45..0f738594 100644 --- a/chore/_xtool/llcppsymg/_cmptest/parse_test/parse.go +++ b/chore/_xtool/llcppsymg/_cmptest/parse_test/parse.go @@ -2,12 +2,15 @@ package main import ( "fmt" + "os" + "path/filepath" "sort" "github.com/goplus/llgo/chore/_xtool/llcppsymg/parse" ) func main() { + TestGenHeaderFilePath() TestNewSymbolProcessor() TestRemovePrefix() TestToGoName() @@ -16,6 +19,64 @@ func main() { TestParseHeaderFile() } +func TestGenHeaderFilePath() { + fmt.Println("=== Test GenHeaderFilePath ===") + + tempDir := os.TempDir() + tempFile1 := filepath.Join(tempDir, "test1.h") + tempFile2 := filepath.Join(tempDir, "test2.h") + os.Create(tempFile1) + os.Create(tempFile2) + defer os.Remove(tempFile1) + defer os.Remove(tempFile2) + + 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: "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) + + result, err := parse.GenHeaderFilePath(tc.cflags, tc.files) + + if err != nil { + fmt.Printf("Error: %v\n", err) + } + 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() + } +} + func TestNewSymbolProcessor() { fmt.Println("=== Test NewSymbolProcessor ===") process := parse.NewSymbolProcessor([]string{"lua_", "luaL_"}) diff --git a/chore/_xtool/llcppsymg/_cmptest/symbol_test/llgo.expect b/chore/_xtool/llcppsymg/_cmptest/symbol_test/llgo.expect index 4a329943..6ca2d4d7 100644 --- a/chore/_xtool/llcppsymg/_cmptest/symbol_test/llgo.expect +++ b/chore/_xtool/llcppsymg/_cmptest/symbol_test/llgo.expect @@ -1,4 +1,21 @@ #stdout +=== Test GenDylibPaths === +Test case: Lua library +Input: -L/opt/homebrew/lib -llua -lm +Output: [/opt/homebrew/lib/liblua.dylib /opt/homebrew/lib/libm.dylib] + +Test case: SQLite library +Input: -L/opt/homebrew/opt/sqlite/lib -lsqlite3 +Output: [/opt/homebrew/opt/sqlite/lib/libsqlite3.dylib] + +Test case: INIReader library +Input: -L/opt/homebrew/Cellar/inih/58/lib -lINIReader +Output: [/opt/homebrew/Cellar/inih/58/lib/libINIReader.dylib] + +Test case: No valid library +Input: -L/opt/homebrew/lib +Error: failed to parse pkg-config output: -L/opt/homebrew/lib + === Test GetCommonSymbols === Test Case: Lua symbols diff --git a/chore/_xtool/llcppsymg/_cmptest/symbol_test/symbol.go b/chore/_xtool/llcppsymg/_cmptest/symbol_test/symbol.go index 81bbe460..272e3224 100644 --- a/chore/_xtool/llcppsymg/_cmptest/symbol_test/symbol.go +++ b/chore/_xtool/llcppsymg/_cmptest/symbol_test/symbol.go @@ -12,10 +12,50 @@ import ( ) func main() { + TestGenDylibPaths() TestGetCommonSymbols() TestReadExistingSymbolTable() TestGenSymbolTableData() } +func TestGenDylibPaths() { + fmt.Println("=== Test GenDylibPaths ===") + + 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: "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) + + result, err := symbol.GenDylibPaths(tc.input) + + if err != nil { + fmt.Printf("Error: %v\n", err) + } else { + fmt.Printf("Output: %v\n", result) + } + fmt.Println() + } +} func TestGetCommonSymbols() { fmt.Println("=== Test GetCommonSymbols ===") testCases := []struct { @@ -67,7 +107,6 @@ func TestGetCommonSymbols() { } fmt.Println() } - func TestReadExistingSymbolTable() { fmt.Println("=== Test ReadExistingSymbolTable ===") diff --git a/chore/_xtool/llcppsymg/clangutils/clangutils.go b/chore/_xtool/llcppsymg/clangutils/clangutils.go index 79770006..e5821b9d 100644 --- a/chore/_xtool/llcppsymg/clangutils/clangutils.go +++ b/chore/_xtool/llcppsymg/clangutils/clangutils.go @@ -75,6 +75,11 @@ func CreateTranslationUnit(config *Config) (*clang.Index, *clang.TranslationUnit 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 func BuildScopingParts(cursor clang.Cursor) []string { var parts []string diff --git a/chore/_xtool/llcppsymg/dylib/dylib.go b/chore/_xtool/llcppsymg/dylib/dylib.go deleted file mode 100644 index 41752e4f..00000000 --- a/chore/_xtool/llcppsymg/dylib/dylib.go +++ /dev/null @@ -1,77 +0,0 @@ -package dylib - -import ( - "fmt" - "os" - "path/filepath" - "strings" - - "github.com/goplus/llgo/xtool/nm" -) - -// 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) { - fmt.Printf("parse dylib symbols from config lib:%s\n", lib) - - dylibPaths, err := GenDylibPaths(lib) - if err != nil { - fmt.Printf("Warning: failed to generate some dylib paths: %v\n", err) - } - - var symbols []*nm.Symbol - var parseErrors []string - - for _, dylibPath := range dylibPaths { - if _, err := os.Stat(dylibPath); err != nil { - fmt.Printf("Warning: Failed to access dylib %s: %v\n", dylibPath, err) - continue - } - - files, err := nm.New("").List(dylibPath) - if err != nil { - parseErrors = append(parseErrors, fmt.Sprintf("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 len(parseErrors) > 0 { - fmt.Printf("Warning: Some libraries could not be parsed: %v\n", parseErrors) - } - return symbols, nil - } - - return nil, fmt.Errorf("no symbols found in any dylib. Errors: %v", parseErrors) -} - -func GenDylibPaths(lib string) ([]string, error) { - parts := strings.Fields(lib) - var libPath, libName string - var dylibPaths []string - - for _, part := range parts { - if strings.HasPrefix(part, "-L") { - libPath = part[2:] - } else if strings.HasPrefix(part, "-l") { - libName = part[2:] - if libPath != "" && libName != "" { - dylibPaths = append(dylibPaths, filepath.Join(libPath, "lib"+libName+".dylib")) - } - } - } - - if len(dylibPaths) == 0 { - return nil, fmt.Errorf("failed to parse pkg-config output: %s", lib) - } - - return dylibPaths, nil -} diff --git a/chore/_xtool/llcppsymg/header/header.go b/chore/_xtool/llcppsymg/header/header.go deleted file mode 100644 index 9390ea2c..00000000 --- a/chore/_xtool/llcppsymg/header/header.go +++ /dev/null @@ -1,42 +0,0 @@ -package header - -import ( - "fmt" - "os" - "path/filepath" - "strings" -) - -func GenHeaderFilePath(cflags string, files []string) ([]string, error) { - prefixPath := strings.TrimPrefix(cflags, "-I") - - var validPaths []string - var errs []string - - for _, file := range files { - if file == "" { - continue - } - fullPath := filepath.Join(prefixPath, file) - if f, err := os.Open(fullPath); err != nil { - if os.IsNotExist(err) { - errs = append(errs, fmt.Sprintf("file not found: %s", file)) - } else { - errs = append(errs, fmt.Sprintf("error accessing file %s: %v", file, err)) - } - } else { - f.Close() - validPaths = append(validPaths, fullPath) - } - } - - if len(validPaths) == 0 && len(errs) == 0 { - return nil, fmt.Errorf("no valid header files") - } - - if len(errs) > 0 { - return validPaths, fmt.Errorf("some files not found or inaccessible: %v", errs) - } - - return validPaths, nil -} diff --git a/chore/_xtool/llcppsymg/llcppsymg.go b/chore/_xtool/llcppsymg/llcppsymg.go index 272efd7a..98fb7617 100644 --- a/chore/_xtool/llcppsymg/llcppsymg.go +++ b/chore/_xtool/llcppsymg/llcppsymg.go @@ -22,8 +22,6 @@ import ( "os" "github.com/goplus/llgo/chore/_xtool/llcppsymg/config" - "github.com/goplus/llgo/chore/_xtool/llcppsymg/dylib" - "github.com/goplus/llgo/chore/_xtool/llcppsymg/header" "github.com/goplus/llgo/chore/_xtool/llcppsymg/parse" "github.com/goplus/llgo/chore/_xtool/llcppsymg/symbol" ) @@ -50,10 +48,10 @@ func main() { if err != nil { fmt.Fprintln(os.Stderr, "Failed to parse config file:", cfgFile) } - symbols, err := dylib.ParseDylibSymbols(conf.Libs) + symbols, err := symbol.ParseDylibSymbols(conf.Libs) check(err) - filepaths, err := header.GenHeaderFilePath(conf.CFlags, conf.Include) + filepaths, err := parse.GenHeaderFilePath(conf.CFlags, conf.Include) check(err) headerInfos, err := parse.ParseHeaderFile(filepaths, conf.TrimPrefixes, conf.Cplusplus, false) check(err) diff --git a/chore/_xtool/llcppsymg/parse/parse.go b/chore/_xtool/llcppsymg/parse/parse.go index 292a8ff7..d7d34a01 100644 --- a/chore/_xtool/llcppsymg/parse/parse.go +++ b/chore/_xtool/llcppsymg/parse/parse.go @@ -2,6 +2,9 @@ package parse import ( "errors" + "fmt" + "os" + "path/filepath" "strconv" "strings" @@ -145,8 +148,7 @@ func (p *SymbolProcessor) visitTop(cursor, parent clang.Cursor) clang.ChildVisit clangutils.VisitChildren(cursor, p.visitTop) case clang.CursorCXXMethod, clang.CursorFunctionDecl, clang.CursorConstructor, clang.CursorDestructor: loc := cursor.Location() - var file clang.File - loc.SpellingLocation(&file, nil, nil, nil) + file, _, _, _ := clangutils.GetLocation(loc) filename := file.FileName() defer filename.Dispose() @@ -185,3 +187,37 @@ func ParseHeaderFile(files []string, Prefixes []string, isCpp bool, isTemp bool) index.Dispose() return processer.SymbolMap, nil } + +func GenHeaderFilePath(cflags string, files []string) ([]string, error) { + prefixPath := strings.TrimPrefix(cflags, "-I") + + var validPaths []string + var errs []string + + for _, file := range files { + if file == "" { + continue + } + fullPath := filepath.Join(prefixPath, file) + if f, err := os.Open(fullPath); err != nil { + if os.IsNotExist(err) { + errs = append(errs, fmt.Sprintf("file not found: %s", file)) + } else { + errs = append(errs, fmt.Sprintf("error accessing file %s: %v", file, err)) + } + } else { + f.Close() + validPaths = append(validPaths, fullPath) + } + } + + if len(validPaths) == 0 && len(errs) == 0 { + return nil, fmt.Errorf("no valid header files") + } + + if len(errs) > 0 { + return validPaths, fmt.Errorf("some files not found or inaccessible: %v", errs) + } + + return validPaths, nil +} diff --git a/chore/_xtool/llcppsymg/symbol/symbol.go b/chore/_xtool/llcppsymg/symbol/symbol.go index 60ab5c6d..c043c8d8 100644 --- a/chore/_xtool/llcppsymg/symbol/symbol.go +++ b/chore/_xtool/llcppsymg/symbol/symbol.go @@ -2,7 +2,9 @@ package symbol import ( "errors" + "fmt" "os" + "path/filepath" "strings" "unsafe" @@ -14,6 +16,73 @@ import ( "github.com/goplus/llgo/xtool/nm" ) +func GenDylibPaths(lib string) ([]string, error) { + parts := strings.Fields(lib) + var libPath, libName string + var dylibPaths []string + + for _, part := range parts { + if strings.HasPrefix(part, "-L") { + libPath = part[2:] + } else if strings.HasPrefix(part, "-l") { + libName = part[2:] + if libPath != "" && libName != "" { + dylibPaths = append(dylibPaths, filepath.Join(libPath, "lib"+libName+".dylib")) + } + } + } + + if len(dylibPaths) == 0 { + return nil, fmt.Errorf("failed to parse pkg-config output: %s", lib) + } + + return dylibPaths, nil +} + +// 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) { + fmt.Printf("parse dylib symbols from config lib:%s\n", lib) + + dylibPaths, err := GenDylibPaths(lib) + if err != nil { + fmt.Printf("Warning: failed to generate some dylib paths: %v\n", err) + } + + var symbols []*nm.Symbol + var parseErrors []string + + for _, dylibPath := range dylibPaths { + if _, err := os.Stat(dylibPath); err != nil { + fmt.Printf("Warning: Failed to access dylib %s: %v\n", dylibPath, err) + continue + } + + files, err := nm.New("").List(dylibPath) + if err != nil { + parseErrors = append(parseErrors, fmt.Sprintf("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 len(parseErrors) > 0 { + fmt.Printf("Warning: Some libraries could not be parsed: %v\n", parseErrors) + } + return symbols, nil + } + + return nil, fmt.Errorf("no symbols found in any dylib. Errors: %v", parseErrors) +} + // 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 { From 7bd3b29a116c6f2a1e04706f184cf7196eafc153 Mon Sep 17 00:00:00 2001 From: luoliwoshang <2643523683@qq.com> Date: Thu, 10 Oct 2024 12:21:20 +0800 Subject: [PATCH 09/15] llcppsymg:remove leading underscores --- .../_xtool/llcppsymg/_cmptest/parse_test/llgo.expect | 8 ++++---- .../_xtool/llcppsymg/_cmptest/symg_test/llgo.expect | 12 ++++++------ chore/_xtool/llcppsymg/parse/parse.go | 8 +++++--- chore/_xtool/llcppsymg/symbol/symbol.go | 2 +- 4 files changed, 16 insertions(+), 14 deletions(-) diff --git a/chore/_xtool/llcppsymg/_cmptest/parse_test/llgo.expect b/chore/_xtool/llcppsymg/_cmptest/parse_test/llgo.expect index 55bb70a2..3dff278d 100644 --- a/chore/_xtool/llcppsymg/_cmptest/parse_test/llgo.expect +++ b/chore/_xtool/llcppsymg/_cmptest/parse_test/llgo.expect @@ -46,10 +46,10 @@ 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 +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: diff --git a/chore/_xtool/llcppsymg/_cmptest/symg_test/llgo.expect b/chore/_xtool/llcppsymg/_cmptest/symg_test/llgo.expect index 93bf120d..064381df 100644 --- a/chore/_xtool/llcppsymg/_cmptest/symg_test/llgo.expect +++ b/chore/_xtool/llcppsymg/_cmptest/symg_test/llgo.expect @@ -1,23 +1,23 @@ #stdout === Test Case: inireader === [{ - "mangle": "_ZN9INIReaderC1EPKc", + "mangle": "ZN9INIReaderC1EPKc", "c++": "INIReader::INIReader(const char *)", "go": "(*Reader).Init" }, { - "mangle": "_ZN9INIReaderC1EPKcl", + "mangle": "ZN9INIReaderC1EPKcl", "c++": "INIReader::INIReader(const char *, long)", "go": "(*Reader).Init__1" }, { - "mangle": "_ZN9INIReaderD1Ev", + "mangle": "ZN9INIReaderD1Ev", "c++": "INIReader::~INIReader()", "go": "(*Reader).Dispose" }, { - "mangle": "_ZNK9INIReader10ParseErrorEv", + "mangle": "ZNK9INIReader10ParseErrorEv", "c++": "INIReader::ParseError()", - "go": "(*Reader).ModifyedParseError" + "go": "(*Reader).ParseError" }, { - "mangle": "_ZNK9INIReader3GetEPKcS1_S1_", + "mangle": "ZNK9INIReader3GetEPKcS1_S1_", "c++": "INIReader::Get(const char *, const char *, const char *)", "go": "(*Reader).Get" }] diff --git a/chore/_xtool/llcppsymg/parse/parse.go b/chore/_xtool/llcppsymg/parse/parse.go index d7d34a01..34f65fce 100644 --- a/chore/_xtool/llcppsymg/parse/parse.go +++ b/chore/_xtool/llcppsymg/parse/parse.go @@ -132,10 +132,12 @@ func (p *SymbolProcessor) collectFuncInfo(cursor clang.Cursor) { symbol := cursor.Mangling() defer symbol.Dispose() + // Remove all leading underscores from C++ symbol names + // On Linux, C++ symbols typically have one leading underscore + // On macOS, C++ symbols may have two leading underscores + // We remove all leading underscores to handle both cases consistently symbolName := c.GoString(symbol.CStr()) - if len(symbolName) >= 1 && symbolName[0] == '_' { - symbolName = symbolName[1:] - } + symbolName = strings.TrimLeft(symbolName, "_") p.SymbolMap[symbolName] = &SymbolInfo{ GoName: p.genGoName(cursor), ProtoName: p.genProtoName(cursor), diff --git a/chore/_xtool/llcppsymg/symbol/symbol.go b/chore/_xtool/llcppsymg/symbol/symbol.go index c043c8d8..a453e095 100644 --- a/chore/_xtool/llcppsymg/symbol/symbol.go +++ b/chore/_xtool/llcppsymg/symbol/symbol.go @@ -88,7 +88,7 @@ func ParseDylibSymbols(lib string) ([]*nm.Symbol, error) { func GetCommonSymbols(dylibSymbols []*nm.Symbol, headerSymbols map[string]*parse.SymbolInfo) []*types.SymbolInfo { var commonSymbols []*types.SymbolInfo for _, dylibSym := range dylibSymbols { - symName := strings.TrimPrefix(dylibSym.Name, "_") + symName := strings.TrimLeft(dylibSym.Name, "_") if symInfo, ok := headerSymbols[symName]; ok { symbolInfo := &types.SymbolInfo{ Mangle: symName, From d5237d1a071703450b2ad27777e2a392a46e3f98 Mon Sep 17 00:00:00 2001 From: luoliwoshang <2643523683@qq.com> Date: Tue, 15 Oct 2024 23:08:20 +0800 Subject: [PATCH 10/15] llcppsymg:multiple dylib path search --- .../_cmptest/symbol_test/llgo.expect | 33 +++-- .../llcppsymg/_cmptest/symbol_test/symbol.go | 129 ++++++++++++++++-- chore/_xtool/llcppsymg/symbol/symbol.go | 56 ++++++-- 3 files changed, 187 insertions(+), 31 deletions(-) diff --git a/chore/_xtool/llcppsymg/_cmptest/symbol_test/llgo.expect b/chore/_xtool/llcppsymg/_cmptest/symbol_test/llgo.expect index 6ca2d4d7..83b9e3fa 100644 --- a/chore/_xtool/llcppsymg/_cmptest/symbol_test/llgo.expect +++ b/chore/_xtool/llcppsymg/_cmptest/symbol_test/llgo.expect @@ -1,21 +1,36 @@ #stdout -=== Test GenDylibPaths === +=== Test ParseLibConfig === Test case: Lua library Input: -L/opt/homebrew/lib -llua -lm -Output: [/opt/homebrew/lib/liblua.dylib /opt/homebrew/lib/libm.dylib] - +Paths: [/opt/homebrew/lib] +Names: [lua m] Test case: SQLite library Input: -L/opt/homebrew/opt/sqlite/lib -lsqlite3 -Output: [/opt/homebrew/opt/sqlite/lib/libsqlite3.dylib] - +Paths: [/opt/homebrew/opt/sqlite/lib] +Names: [sqlite3] Test case: INIReader library Input: -L/opt/homebrew/Cellar/inih/58/lib -lINIReader -Output: [/opt/homebrew/Cellar/inih/58/lib/libINIReader.dylib] - +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 -Error: failed to parse pkg-config output: -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: no existing dylib +Warning: Some libraries were not found: notexist === Test GetCommonSymbols === Test Case: Lua symbols diff --git a/chore/_xtool/llcppsymg/_cmptest/symbol_test/symbol.go b/chore/_xtool/llcppsymg/_cmptest/symbol_test/symbol.go index 272e3224..192f2427 100644 --- a/chore/_xtool/llcppsymg/_cmptest/symbol_test/symbol.go +++ b/chore/_xtool/llcppsymg/_cmptest/symbol_test/symbol.go @@ -3,7 +3,10 @@ package main import ( "fmt" "os" + "path/filepath" + "runtime" "sort" + "strings" "github.com/goplus/llgo/chore/_xtool/llcppsymg/parse" "github.com/goplus/llgo/chore/_xtool/llcppsymg/symbol" @@ -12,13 +15,14 @@ import ( ) func main() { + TestParseLibConfig() TestGenDylibPaths() TestGetCommonSymbols() TestReadExistingSymbolTable() TestGenSymbolTableData() } -func TestGenDylibPaths() { - fmt.Println("=== Test GenDylibPaths ===") +func TestParseLibConfig() { + fmt.Println("=== Test ParseLibConfig ===") testCases := []struct { name string @@ -36,6 +40,10 @@ func TestGenDylibPaths() { 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", @@ -46,16 +54,119 @@ func TestGenDylibPaths() { fmt.Printf("Test case: %s\n", tc.name) fmt.Printf("Input: %s\n", tc.input) - result, err := symbol.GenDylibPaths(tc.input) + conf := symbol.ParseLibConfig(tc.input) - if err != nil { - fmt.Printf("Error: %v\n", err) - } else { - fmt.Printf("Output: %v\n", result) - } - fmt.Println() + 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 *symbol.LibConfig + defaultPaths []string + want []string + wantErr bool + }{ + { + name: "existing dylib", + conf: &symbol.LibConfig{ + Names: []string{"symb1"}, + Paths: []string{tempDir}, + }, + defaultPaths: []string{}, + want: []string{dylib1}, + }, + { + name: "existing dylibs", + conf: &symbol.LibConfig{ + Names: []string{"symb1", "symb2"}, + Paths: []string{tempDir}, + }, + defaultPaths: []string{}, + want: []string{dylib1, dylib2}, + }, + { + name: "existint default paths", + conf: &symbol.LibConfig{ + Names: []string{"symb1", "symb3"}, + Paths: []string{tempDir}, + }, + defaultPaths: []string{tempDefaultPath}, + want: []string{dylib1, defaultDylib3}, + }, + { + name: "no existing dylib", + conf: &symbol.LibConfig{ + Names: []string{"notexist"}, + Paths: []string{tempDir}, + }, + want: []string{}, + wantErr: true, + }, + } + for _, tc := range testCase { + fmt.Printf("Test case: %s\n", tc.name) + paths, err := symbol.GenDylibPaths(tc.conf, tc.defaultPaths) + + if tc.wantErr { + if err == nil { + fmt.Printf("Expected error, but got nil\n") + } + } else { + if err != nil { + fmt.Printf("Unexpected 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 TestGetCommonSymbols() { fmt.Println("=== Test GetCommonSymbols ===") testCases := []struct { diff --git a/chore/_xtool/llcppsymg/symbol/symbol.go b/chore/_xtool/llcppsymg/symbol/symbol.go index a453e095..2548bd86 100644 --- a/chore/_xtool/llcppsymg/symbol/symbol.go +++ b/chore/_xtool/llcppsymg/symbol/symbol.go @@ -5,6 +5,7 @@ import ( "fmt" "os" "path/filepath" + "runtime" "strings" "unsafe" @@ -16,27 +17,55 @@ import ( "github.com/goplus/llgo/xtool/nm" ) -func GenDylibPaths(lib string) ([]string, error) { +type LibConfig struct { + Paths []string + Names []string +} + +func ParseLibConfig(lib string) *LibConfig { parts := strings.Fields(lib) - var libPath, libName string - var dylibPaths []string + config := &LibConfig{} for _, part := range parts { if strings.HasPrefix(part, "-L") { - libPath = part[2:] + config.Paths = append(config.Paths, part[2:]) } else if strings.HasPrefix(part, "-l") { - libName = part[2:] - if libPath != "" && libName != "" { - dylibPaths = append(dylibPaths, filepath.Join(libPath, "lib"+libName+".dylib")) - } + config.Names = append(config.Names, part[2:]) } } - if len(dylibPaths) == 0 { - return nil, fmt.Errorf("failed to parse pkg-config output: %s", lib) - } + return config +} - return dylibPaths, nil +func GenDylibPaths(config *LibConfig, defaultPaths []string) ([]string, error) { + var foundPaths []string + var notFound []string + affix := ".dylib" + if runtime.GOOS == "linux" { + affix = ".so" + } + searchPaths := append(config.Paths, defaultPaths...) + for _, name := range config.Names { + var foundPath string + for _, path := range searchPaths { + dylibPath := filepath.Join(path, "lib"+name+affix) + if _, err := os.Stat(dylibPath); err == nil { + foundPath = dylibPath + } + } + if foundPath != "" { + foundPaths = append(foundPaths, foundPath) + } else { + notFound = append(notFound, name) + } + } + if len(notFound) > 0 { + fmt.Printf("Warning: Some libraries were not found: %s\n", strings.Join(notFound, ", ")) + } + if len(foundPaths) == 0 { + return nil, fmt.Errorf("failed to find any libraries") + } + return foundPaths, nil } // ParseDylibSymbols parses symbols from dynamic libraries specified in the lib string. @@ -48,7 +77,8 @@ func GenDylibPaths(lib string) ([]string, error) { func ParseDylibSymbols(lib string) ([]*nm.Symbol, error) { fmt.Printf("parse dylib symbols from config lib:%s\n", lib) - dylibPaths, err := GenDylibPaths(lib) + conf := ParseLibConfig(lib) + dylibPaths, err := GenDylibPaths(conf, []string{}) if err != nil { fmt.Printf("Warning: failed to generate some dylib paths: %v\n", err) } From 7fe9c9366e70e16a63c69e68203e63b071a08825 Mon Sep 17 00:00:00 2001 From: luoliwoshang <2643523683@qq.com> Date: Wed, 16 Oct 2024 15:08:17 +0800 Subject: [PATCH 11/15] llcppsymg:linux sys path --- chore/_xtool/llcppsymg/symbol/symbol.go | 45 ++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/chore/_xtool/llcppsymg/symbol/symbol.go b/chore/_xtool/llcppsymg/symbol/symbol.go index 2548bd86..9cabe960 100644 --- a/chore/_xtool/llcppsymg/symbol/symbol.go +++ b/chore/_xtool/llcppsymg/symbol/symbol.go @@ -78,7 +78,8 @@ func ParseDylibSymbols(lib string) ([]*nm.Symbol, error) { fmt.Printf("parse dylib symbols from config lib:%s\n", lib) conf := ParseLibConfig(lib) - dylibPaths, err := GenDylibPaths(conf, []string{}) + defaultPaths := getSysLibPaths() + dylibPaths, err := GenDylibPaths(conf, defaultPaths) if err != nil { fmt.Printf("Warning: failed to generate some dylib paths: %v\n", err) } @@ -113,6 +114,48 @@ func ParseDylibSymbols(lib string) ([]*nm.Symbol, error) { return nil, fmt.Errorf("no symbols found in any dylib. Errors: %v", parseErrors) } +func getSysLibPaths() []string { + var paths []string + if runtime.GOOS == "linux" { + paths = []string{ + "/usr/lib", + "/usr/local/lib", + } + paths = append(paths, getPath("/etc/ld.so.conf")...) + confd := "/etc/ld.so.conf.d" + if dir, err := os.Stat(confd); err == nil && dir.IsDir() { + _ = dir + // 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 { + 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 { From 905ed36afd2035886dc959e51646063d6ef94977 Mon Sep 17 00:00:00 2001 From: luoliwoshang <2643523683@qq.com> Date: Thu, 17 Oct 2024 18:54:28 +0800 Subject: [PATCH 12/15] llcppsymg:symbol debug log --- .../_cmptest/symbol_test/llgo.expect | 1 - chore/_xtool/llcppsymg/llcppsymg.go | 39 +++++- chore/_xtool/llcppsymg/symbol/symbol.go | 121 ++++++++++++++---- 3 files changed, 131 insertions(+), 30 deletions(-) diff --git a/chore/_xtool/llcppsymg/_cmptest/symbol_test/llgo.expect b/chore/_xtool/llcppsymg/_cmptest/symbol_test/llgo.expect index 83b9e3fa..69d5c78f 100644 --- a/chore/_xtool/llcppsymg/_cmptest/symbol_test/llgo.expect +++ b/chore/_xtool/llcppsymg/_cmptest/symbol_test/llgo.expect @@ -30,7 +30,6 @@ Test case: existint default paths Path libsymb1 is in the expected paths Path libsymb3 is in the expected paths Test case: no existing dylib -Warning: Some libraries were not found: notexist === Test GetCommonSymbols === Test Case: Lua symbols diff --git a/chore/_xtool/llcppsymg/llcppsymg.go b/chore/_xtool/llcppsymg/llcppsymg.go index 98fb7617..2594e90a 100644 --- a/chore/_xtool/llcppsymg/llcppsymg.go +++ b/chore/_xtool/llcppsymg/llcppsymg.go @@ -20,6 +20,7 @@ import ( "fmt" "io" "os" + "strings" "github.com/goplus/llgo/chore/_xtool/llcppsymg/config" "github.com/goplus/llgo/chore/_xtool/llcppsymg/parse" @@ -29,22 +30,49 @@ import ( func main() { cfgFile := "llcppg.cfg" symbFile := "llcppg.symb.json" - if len(os.Args) > 1 { - cfgFile = os.Args[1] + verbose := false + readStdin := false + + for i := 1; i < len(os.Args); i++ { + arg := os.Args[i] + if arg == "-" { + readStdin = true + } else if arg == "-v" { + verbose = true + } else if !strings.HasPrefix(arg, "-") { + cfgFile = arg + break + } } var data []byte var err error - if cfgFile == "-" { + if readStdin { data, err = io.ReadAll(os.Stdin) } else { data, err = os.ReadFile(cfgFile) } + check(err) conf, err := config.GetConf(data) check(err) defer conf.Delete() + if verbose { + symbol.SetDebug(symbol.DbgFlagAll) + if readStdin { + fmt.Println("Config From Stdin") + } else { + fmt.Println("Config From File", 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) + } + if err != nil { fmt.Fprintln(os.Stderr, "Failed to parse config file:", cfgFile) } @@ -53,6 +81,11 @@ func main() { filepaths, err := parse.GenHeaderFilePath(conf.CFlags, conf.Include) check(err) + + if verbose { + fmt.Println("filepaths", filepaths) + } + headerInfos, err := parse.ParseHeaderFile(filepaths, conf.TrimPrefixes, conf.Cplusplus, false) check(err) diff --git a/chore/_xtool/llcppsymg/symbol/symbol.go b/chore/_xtool/llcppsymg/symbol/symbol.go index 9cabe960..0a49936b 100644 --- a/chore/_xtool/llcppsymg/symbol/symbol.go +++ b/chore/_xtool/llcppsymg/symbol/symbol.go @@ -17,6 +17,25 @@ import ( "github.com/goplus/llgo/xtool/nm" ) +type dbgFlags = int + +const ( + DbgConf dbgFlags = 1 << iota + DbgSymbol + + DbgFlagAll = DbgConf | DbgSymbol +) + +var ( + debugConf bool + debugSymbol bool +) + +func SetDebug(dbgFlags dbgFlags) { + debugConf = (dbgFlags & DbgConf) != 0 + debugSymbol = (dbgFlags & DbgSymbol) != 0 +} + type LibConfig struct { Paths []string Names []string @@ -59,7 +78,7 @@ func GenDylibPaths(config *LibConfig, defaultPaths []string) ([]string, error) { notFound = append(notFound, name) } } - if len(notFound) > 0 { + if len(notFound) > 0 && debugSymbol { fmt.Printf("Warning: Some libraries were not found: %s\n", strings.Join(notFound, ", ")) } if len(foundPaths) == 0 { @@ -75,13 +94,23 @@ func GenDylibPaths(config *LibConfig, defaultPaths []string) ([]string, error) { // // Returns symbols and nil error if any symbols are found, or nil and error if none found. func ParseDylibSymbols(lib string) ([]*nm.Symbol, error) { - fmt.Printf("parse dylib symbols from config lib:%s\n", lib) - + if debugConf { + fmt.Println("ParseDylibSymbols:from", lib) + } conf := ParseLibConfig(lib) + if debugConf { + fmt.Println("ParseDylibSymbols:LibConfig Parse To") + fmt.Println("conf.Names: ", conf.Names) + fmt.Println("conf.Paths: ", conf.Paths) + } defaultPaths := getSysLibPaths() dylibPaths, err := GenDylibPaths(conf, defaultPaths) if err != nil { - fmt.Printf("Warning: failed to generate some dylib paths: %v\n", err) + return nil, fmt.Errorf("failed to generate some dylib paths: %v", err) + } + + if debugSymbol { + fmt.Println("ParseDylibSymbols:dylibPaths", dylibPaths) } var symbols []*nm.Symbol @@ -89,13 +118,15 @@ func ParseDylibSymbols(lib string) ([]*nm.Symbol, error) { for _, dylibPath := range dylibPaths { if _, err := os.Stat(dylibPath); err != nil { - fmt.Printf("Warning: Failed to access dylib %s: %v\n", dylibPath, err) + 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("Failed to list symbols in dylib %s: %v", dylibPath, err)) + parseErrors = append(parseErrors, fmt.Sprintf("ParseDylibSymbols:Failed to list symbols in dylib %s: %v", dylibPath, err)) continue } @@ -105,8 +136,11 @@ func ParseDylibSymbols(lib string) ([]*nm.Symbol, error) { } if len(symbols) > 0 { - if len(parseErrors) > 0 { - fmt.Printf("Warning: Some libraries could not be parsed: %v\n", parseErrors) + 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 } @@ -117,28 +151,41 @@ func ParseDylibSymbols(lib string) ([]*nm.Symbol, error) { func getSysLibPaths() []string { var paths []string if runtime.GOOS == "linux" { + if debugConf { + fmt.Println("getSysLibPaths:find sys lib path from linux") + } paths = []string{ "/usr/lib", "/usr/local/lib", } paths = append(paths, getPath("/etc/ld.so.conf")...) - confd := "/etc/ld.so.conf.d" - if dir, err := os.Stat(confd); err == nil && dir.IsDir() { - _ = dir - // 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)...) - // } - // } + if debugConf && 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 debugConf { + 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 debugConf { + fmt.Println("getPath:from", file) + } var paths []string content, err := os.ReadFile(file) if err != nil { @@ -206,12 +253,28 @@ func ReadExistingSymbolTable(fileName string) (map[string]types.SymbolInfo, bool } func GenSymbolTableData(commonSymbols []*types.SymbolInfo, existingSymbols map[string]types.SymbolInfo) ([]byte, error) { - // todo(zzy): len(existingSymbols) !=0 - // https://github.com/goplus/llgo/issues/808 will cause unexpected panic - // https://github.com/goplus/llgo/pull/793 this pr can fix it - for i := range commonSymbols { - if existingSymbol, exists := existingSymbols[commonSymbols[i].Mangle]; exists { - commonSymbols[i].Go = existingSymbol.Go + 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) + } } } @@ -237,8 +300,14 @@ func GenSymbolTableData(commonSymbols []*types.SymbolInfo, existingSymbols map[s 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, _ := ReadExistingSymbolTable(symbFile) + 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 { From 151d3a9610e9575159a38671f26260b0e74d2569 Mon Sep 17 00:00:00 2001 From: luoliwoshang <2643523683@qq.com> Date: Fri, 18 Oct 2024 11:25:53 +0800 Subject: [PATCH 13/15] llcppsymg:libs,cflags parse --- .../llcppsymg/_cmptest/config_test/config.go | 269 ++++++++++++++++++ .../_cmptest/config_test/llgo.expect | 73 +++++ .../llcppsymg/_cmptest/parse_test/llgo.expect | 18 -- .../llcppsymg/_cmptest/parse_test/parse.go | 61 ---- .../_cmptest/symbol_test/llgo.expect | 31 -- .../llcppsymg/_cmptest/symbol_test/symbol.go | 150 ---------- .../_xtool/llcppsymg/config/cfgparse/parse.go | 109 +++++++ chore/_xtool/llcppsymg/llcppsymg.go | 9 +- chore/_xtool/llcppsymg/parse/parse.go | 37 --- chore/_xtool/llcppsymg/symbol/symbol.go | 88 ++---- 10 files changed, 477 insertions(+), 368 deletions(-) create mode 100644 chore/_xtool/llcppsymg/config/cfgparse/parse.go diff --git a/chore/_xtool/llcppsymg/_cmptest/config_test/config.go b/chore/_xtool/llcppsymg/_cmptest/config_test/config.go index 6ef2e2d1..4b3bdbc1 100644 --- a/chore/_xtool/llcppsymg/_cmptest/config_test/config.go +++ b/chore/_xtool/llcppsymg/_cmptest/config_test/config.go @@ -2,13 +2,21 @@ 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() { @@ -61,3 +69,264 @@ func TestGetConf() { 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() + } +} diff --git a/chore/_xtool/llcppsymg/_cmptest/config_test/llgo.expect b/chore/_xtool/llcppsymg/_cmptest/config_test/llgo.expect index acbf814f..a7c57a05 100644 --- a/chore/_xtool/llcppsymg/_cmptest/config_test/llgo.expect +++ b/chore/_xtool/llcppsymg/_cmptest/config_test/llgo.expect @@ -18,6 +18,79 @@ 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 diff --git a/chore/_xtool/llcppsymg/_cmptest/parse_test/llgo.expect b/chore/_xtool/llcppsymg/_cmptest/parse_test/llgo.expect index 3dff278d..5f282756 100644 --- a/chore/_xtool/llcppsymg/_cmptest/parse_test/llgo.expect +++ b/chore/_xtool/llcppsymg/_cmptest/parse_test/llgo.expect @@ -1,22 +1,4 @@ #stdout -=== 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] -Error: some files not found or inaccessible: [file not found: nonexistent.h] -Output: [test1.h] - -Test case: No existing files -Input files: [nonexistent1.h nonexistent2.h] -Error: some files not found or inaccessible: [file not found: nonexistent1.h file not found: nonexistent2.h] - -Test case: Empty file list -Input files: [] -Error: no valid header files - === Test NewSymbolProcessor === Before: No prefixes After: Prefixes: [lua_ luaL_] diff --git a/chore/_xtool/llcppsymg/_cmptest/parse_test/parse.go b/chore/_xtool/llcppsymg/_cmptest/parse_test/parse.go index 0f738594..4a827b45 100644 --- a/chore/_xtool/llcppsymg/_cmptest/parse_test/parse.go +++ b/chore/_xtool/llcppsymg/_cmptest/parse_test/parse.go @@ -2,15 +2,12 @@ package main import ( "fmt" - "os" - "path/filepath" "sort" "github.com/goplus/llgo/chore/_xtool/llcppsymg/parse" ) func main() { - TestGenHeaderFilePath() TestNewSymbolProcessor() TestRemovePrefix() TestToGoName() @@ -19,64 +16,6 @@ func main() { TestParseHeaderFile() } -func TestGenHeaderFilePath() { - fmt.Println("=== Test GenHeaderFilePath ===") - - tempDir := os.TempDir() - tempFile1 := filepath.Join(tempDir, "test1.h") - tempFile2 := filepath.Join(tempDir, "test2.h") - os.Create(tempFile1) - os.Create(tempFile2) - defer os.Remove(tempFile1) - defer os.Remove(tempFile2) - - 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: "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) - - result, err := parse.GenHeaderFilePath(tc.cflags, tc.files) - - if err != nil { - fmt.Printf("Error: %v\n", err) - } - 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() - } -} - func TestNewSymbolProcessor() { fmt.Println("=== Test NewSymbolProcessor ===") process := parse.NewSymbolProcessor([]string{"lua_", "luaL_"}) diff --git a/chore/_xtool/llcppsymg/_cmptest/symbol_test/llgo.expect b/chore/_xtool/llcppsymg/_cmptest/symbol_test/llgo.expect index 69d5c78f..4a329943 100644 --- a/chore/_xtool/llcppsymg/_cmptest/symbol_test/llgo.expect +++ b/chore/_xtool/llcppsymg/_cmptest/symbol_test/llgo.expect @@ -1,35 +1,4 @@ #stdout -=== Test ParseLibConfig === -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: no existing dylib === Test GetCommonSymbols === Test Case: Lua symbols diff --git a/chore/_xtool/llcppsymg/_cmptest/symbol_test/symbol.go b/chore/_xtool/llcppsymg/_cmptest/symbol_test/symbol.go index 192f2427..357fddd4 100644 --- a/chore/_xtool/llcppsymg/_cmptest/symbol_test/symbol.go +++ b/chore/_xtool/llcppsymg/_cmptest/symbol_test/symbol.go @@ -3,10 +3,7 @@ package main import ( "fmt" "os" - "path/filepath" - "runtime" "sort" - "strings" "github.com/goplus/llgo/chore/_xtool/llcppsymg/parse" "github.com/goplus/llgo/chore/_xtool/llcppsymg/symbol" @@ -15,158 +12,11 @@ import ( ) func main() { - TestParseLibConfig() - TestGenDylibPaths() TestGetCommonSymbols() TestReadExistingSymbolTable() TestGenSymbolTableData() } -func TestParseLibConfig() { - fmt.Println("=== Test ParseLibConfig ===") - 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 := symbol.ParseLibConfig(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 *symbol.LibConfig - defaultPaths []string - want []string - wantErr bool - }{ - { - name: "existing dylib", - conf: &symbol.LibConfig{ - Names: []string{"symb1"}, - Paths: []string{tempDir}, - }, - defaultPaths: []string{}, - want: []string{dylib1}, - }, - { - name: "existing dylibs", - conf: &symbol.LibConfig{ - Names: []string{"symb1", "symb2"}, - Paths: []string{tempDir}, - }, - defaultPaths: []string{}, - want: []string{dylib1, dylib2}, - }, - { - name: "existint default paths", - conf: &symbol.LibConfig{ - Names: []string{"symb1", "symb3"}, - Paths: []string{tempDir}, - }, - defaultPaths: []string{tempDefaultPath}, - want: []string{dylib1, defaultDylib3}, - }, - { - name: "no existing dylib", - conf: &symbol.LibConfig{ - Names: []string{"notexist"}, - Paths: []string{tempDir}, - }, - want: []string{}, - wantErr: true, - }, - } - for _, tc := range testCase { - fmt.Printf("Test case: %s\n", tc.name) - paths, err := symbol.GenDylibPaths(tc.conf, tc.defaultPaths) - - if tc.wantErr { - if err == nil { - fmt.Printf("Expected error, but got nil\n") - } - } else { - if err != nil { - fmt.Printf("Unexpected 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 TestGetCommonSymbols() { fmt.Println("=== Test GetCommonSymbols ===") testCases := []struct { diff --git a/chore/_xtool/llcppsymg/config/cfgparse/parse.go b/chore/_xtool/llcppsymg/config/cfgparse/parse.go new file mode 100644 index 00000000..6bac7430 --- /dev/null +++ b/chore/_xtool/llcppsymg/config/cfgparse/parse.go @@ -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 +} diff --git a/chore/_xtool/llcppsymg/llcppsymg.go b/chore/_xtool/llcppsymg/llcppsymg.go index 2594e90a..f9de88d4 100644 --- a/chore/_xtool/llcppsymg/llcppsymg.go +++ b/chore/_xtool/llcppsymg/llcppsymg.go @@ -23,6 +23,7 @@ import ( "strings" "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/symbol" ) @@ -79,11 +80,15 @@ func main() { symbols, err := symbol.ParseDylibSymbols(conf.Libs) check(err) - filepaths, err := parse.GenHeaderFilePath(conf.CFlags, conf.Include) + cflag := cfgparse.ParseCFlags(conf.CFlags) + filepaths, notFounds, err := cflag.GenHeaderFilePaths(conf.Include) check(err) if verbose { - fmt.Println("filepaths", filepaths) + fmt.Println("header file paths", filepaths) + if len(notFounds) > 0 { + fmt.Println("not found header files", notFounds) + } } headerInfos, err := parse.ParseHeaderFile(filepaths, conf.TrimPrefixes, conf.Cplusplus, false) diff --git a/chore/_xtool/llcppsymg/parse/parse.go b/chore/_xtool/llcppsymg/parse/parse.go index 34f65fce..6ebd62fe 100644 --- a/chore/_xtool/llcppsymg/parse/parse.go +++ b/chore/_xtool/llcppsymg/parse/parse.go @@ -2,9 +2,6 @@ package parse import ( "errors" - "fmt" - "os" - "path/filepath" "strconv" "strings" @@ -189,37 +186,3 @@ func ParseHeaderFile(files []string, Prefixes []string, isCpp bool, isTemp bool) index.Dispose() return processer.SymbolMap, nil } - -func GenHeaderFilePath(cflags string, files []string) ([]string, error) { - prefixPath := strings.TrimPrefix(cflags, "-I") - - var validPaths []string - var errs []string - - for _, file := range files { - if file == "" { - continue - } - fullPath := filepath.Join(prefixPath, file) - if f, err := os.Open(fullPath); err != nil { - if os.IsNotExist(err) { - errs = append(errs, fmt.Sprintf("file not found: %s", file)) - } else { - errs = append(errs, fmt.Sprintf("error accessing file %s: %v", file, err)) - } - } else { - f.Close() - validPaths = append(validPaths, fullPath) - } - } - - if len(validPaths) == 0 && len(errs) == 0 { - return nil, fmt.Errorf("no valid header files") - } - - if len(errs) > 0 { - return validPaths, fmt.Errorf("some files not found or inaccessible: %v", errs) - } - - return validPaths, nil -} diff --git a/chore/_xtool/llcppsymg/symbol/symbol.go b/chore/_xtool/llcppsymg/symbol/symbol.go index 0a49936b..ba556a6d 100644 --- a/chore/_xtool/llcppsymg/symbol/symbol.go +++ b/chore/_xtool/llcppsymg/symbol/symbol.go @@ -4,7 +4,6 @@ import ( "errors" "fmt" "os" - "path/filepath" "runtime" "strings" "unsafe" @@ -12,6 +11,7 @@ import ( "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" @@ -20,73 +20,18 @@ import ( type dbgFlags = int const ( - DbgConf dbgFlags = 1 << iota - DbgSymbol - - DbgFlagAll = DbgConf | DbgSymbol + DbgSymbol dbgFlags = 1 << iota + DbgFlagAll = DbgSymbol ) var ( - debugConf bool debugSymbol bool ) func SetDebug(dbgFlags dbgFlags) { - debugConf = (dbgFlags & DbgConf) != 0 debugSymbol = (dbgFlags & DbgSymbol) != 0 } -type LibConfig struct { - Paths []string - Names []string -} - -func ParseLibConfig(lib string) *LibConfig { - parts := strings.Fields(lib) - config := &LibConfig{} - - for _, part := range parts { - if strings.HasPrefix(part, "-L") { - config.Paths = append(config.Paths, part[2:]) - } else if strings.HasPrefix(part, "-l") { - config.Names = append(config.Names, part[2:]) - } - } - - return config -} - -func GenDylibPaths(config *LibConfig, defaultPaths []string) ([]string, error) { - var foundPaths []string - var notFound []string - affix := ".dylib" - if runtime.GOOS == "linux" { - affix = ".so" - } - searchPaths := append(config.Paths, defaultPaths...) - for _, name := range config.Names { - var foundPath string - for _, path := range searchPaths { - dylibPath := filepath.Join(path, "lib"+name+affix) - if _, err := os.Stat(dylibPath); err == nil { - foundPath = dylibPath - } - } - if foundPath != "" { - foundPaths = append(foundPaths, foundPath) - } else { - notFound = append(notFound, name) - } - } - if len(notFound) > 0 && debugSymbol { - fmt.Printf("Warning: Some libraries were not found: %s\n", strings.Join(notFound, ", ")) - } - if len(foundPaths) == 0 { - return nil, fmt.Errorf("failed to find any libraries") - } - return foundPaths, nil -} - // 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 @@ -94,23 +39,28 @@ func GenDylibPaths(config *LibConfig, defaultPaths []string) ([]string, error) { // // 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 debugConf { + if debugSymbol { fmt.Println("ParseDylibSymbols:from", lib) } - conf := ParseLibConfig(lib) - if debugConf { + sysPaths := getSysLibPaths() + lbs := cfgparse.ParseLibs(lib) + if debugSymbol { fmt.Println("ParseDylibSymbols:LibConfig Parse To") - fmt.Println("conf.Names: ", conf.Names) - fmt.Println("conf.Paths: ", conf.Paths) + fmt.Println("libs.Names: ", lbs.Names) + fmt.Println("libs.Paths: ", lbs.Paths) } - defaultPaths := getSysLibPaths() - dylibPaths, err := GenDylibPaths(conf, defaultPaths) + 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 @@ -151,7 +101,7 @@ func ParseDylibSymbols(lib string) ([]*nm.Symbol, error) { func getSysLibPaths() []string { var paths []string if runtime.GOOS == "linux" { - if debugConf { + if debugSymbol { fmt.Println("getSysLibPaths:find sys lib path from linux") } paths = []string{ @@ -159,13 +109,13 @@ func getSysLibPaths() []string { "/usr/local/lib", } paths = append(paths, getPath("/etc/ld.so.conf")...) - if debugConf && len(paths) == 0 { + 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 debugConf { + if debugSymbol { fmt.Println("getSysLibPaths:/etc/ld.so.conf.d not found or not dir") } return paths @@ -183,7 +133,7 @@ func getSysLibPaths() []string { } func getPath(file string) []string { - if debugConf { + if debugSymbol { fmt.Println("getPath:from", file) } var paths []string From e6bfe1fc88f17391517c6b834a3f577d084aafcb Mon Sep 17 00:00:00 2001 From: luoliwoshang <2643523683@qq.com> Date: Sat, 19 Oct 2024 00:04:58 +0800 Subject: [PATCH 14/15] llcppsymg:refine args --- chore/_xtool/llcppsymg/llcppsymg.go | 31 +++++++++-------------------- 1 file changed, 9 insertions(+), 22 deletions(-) diff --git a/chore/_xtool/llcppsymg/llcppsymg.go b/chore/_xtool/llcppsymg/llcppsymg.go index f9de88d4..63ca3d4b 100644 --- a/chore/_xtool/llcppsymg/llcppsymg.go +++ b/chore/_xtool/llcppsymg/llcppsymg.go @@ -20,8 +20,8 @@ import ( "fmt" "io" "os" - "strings" + "github.com/goplus/llgo/chore/_xtool/llcppsymg/args" "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" @@ -29,29 +29,16 @@ import ( ) func main() { - cfgFile := "llcppg.cfg" symbFile := "llcppg.symb.json" - verbose := false - readStdin := false - for i := 1; i < len(os.Args); i++ { - arg := os.Args[i] - if arg == "-" { - readStdin = true - } else if arg == "-v" { - verbose = true - } else if !strings.HasPrefix(arg, "-") { - cfgFile = arg - break - } - } + ags, _ := args.ParseArgs(os.Args[1:], nil) var data []byte var err error - if readStdin { + if ags.UseStdin { data, err = io.ReadAll(os.Stdin) } else { - data, err = os.ReadFile(cfgFile) + data, err = os.ReadFile(ags.CfgFile) } check(err) @@ -59,12 +46,12 @@ func main() { check(err) defer conf.Delete() - if verbose { + if ags.Verbose { symbol.SetDebug(symbol.DbgFlagAll) - if readStdin { + if ags.UseStdin { fmt.Println("Config From Stdin") } else { - fmt.Println("Config From File", cfgFile) + fmt.Println("Config From File", ags.CfgFile) } fmt.Println("Name:", conf.Name) fmt.Println("CFlags:", conf.CFlags) @@ -75,7 +62,7 @@ func main() { } if err != nil { - fmt.Fprintln(os.Stderr, "Failed to parse config file:", cfgFile) + fmt.Fprintln(os.Stderr, "Failed to parse config file:", ags.CfgFile) } symbols, err := symbol.ParseDylibSymbols(conf.Libs) check(err) @@ -84,7 +71,7 @@ func main() { filepaths, notFounds, err := cflag.GenHeaderFilePaths(conf.Include) check(err) - if verbose { + if ags.Verbose { fmt.Println("header file paths", filepaths) if len(notFounds) > 0 { fmt.Println("not found header files", notFounds) From 7747082ae8b78236c3e679234abefe0f25c1bb40 Mon Sep 17 00:00:00 2001 From: luoliwoshang <2643523683@qq.com> Date: Sat, 26 Oct 2024 18:43:14 +0800 Subject: [PATCH 15/15] llcppsymg:remove one leading underscore only in mac --- .../llcppsymg/_cmptest/parse_test/llgo.expect | 8 +++--- .../_cmptest/symbol_test/llgo.expect | 6 ++--- .../llcppsymg/_cmptest/symbol_test/symbol.go | 26 +++++++++---------- .../llcppsymg/_cmptest/symg_test/llgo.expect | 12 ++++----- .../llcppsymg/_cmptest/symg_test/symg.go | 18 ++++++------- chore/_xtool/llcppsymg/parse/parse.go | 8 +++--- chore/_xtool/llcppsymg/symbol/symbol.go | 17 +++++++++++- 7 files changed, 56 insertions(+), 39 deletions(-) diff --git a/chore/_xtool/llcppsymg/_cmptest/parse_test/llgo.expect b/chore/_xtool/llcppsymg/_cmptest/parse_test/llgo.expect index 5f282756..819ff4b8 100644 --- a/chore/_xtool/llcppsymg/_cmptest/parse_test/llgo.expect +++ b/chore/_xtool/llcppsymg/_cmptest/parse_test/llgo.expect @@ -28,10 +28,10 @@ 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 +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: diff --git a/chore/_xtool/llcppsymg/_cmptest/symbol_test/llgo.expect b/chore/_xtool/llcppsymg/_cmptest/symbol_test/llgo.expect index 4a329943..8372439b 100644 --- a/chore/_xtool/llcppsymg/_cmptest/symbol_test/llgo.expect +++ b/chore/_xtool/llcppsymg/_cmptest/symbol_test/llgo.expect @@ -10,9 +10,9 @@ Mangle: lua_callk, CPP: lua_callk(lua_State *, int, int, lua_KContext, lua_KFunc 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 +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: diff --git a/chore/_xtool/llcppsymg/_cmptest/symbol_test/symbol.go b/chore/_xtool/llcppsymg/_cmptest/symbol_test/symbol.go index 357fddd4..929921cd 100644 --- a/chore/_xtool/llcppsymg/_cmptest/symbol_test/symbol.go +++ b/chore/_xtool/llcppsymg/_cmptest/symbol_test/symbol.go @@ -27,11 +27,11 @@ func TestGetCommonSymbols() { { name: "Lua symbols", dylibSymbols: []*nm.Symbol{ - {Name: "_lua_absindex"}, - {Name: "_lua_arith"}, - {Name: "_lua_atpanic"}, - {Name: "_lua_callk"}, - {Name: "_lua_lib_nonexistent"}, + {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"}, @@ -44,16 +44,16 @@ func TestGetCommonSymbols() { { name: "INIReader and Std library symbols", dylibSymbols: []*nm.Symbol{ - {Name: "_ZNK9INIReader12GetInteger64ERKNSt3__112basic_stringIcNS0_11char_traitsIcEENS0_9allocatorIcEEEES8_x"}, - {Name: "_ZNK9INIReader7GetRealERKNSt3__112basic_stringIcNS0_11char_traitsIcEENS0_9allocatorIcEEEES8_d"}, - {Name: "_ZNK9INIReader10ParseErrorEv"}, + {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)"}, + "_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)"}, }, }, } diff --git a/chore/_xtool/llcppsymg/_cmptest/symg_test/llgo.expect b/chore/_xtool/llcppsymg/_cmptest/symg_test/llgo.expect index 064381df..93bf120d 100644 --- a/chore/_xtool/llcppsymg/_cmptest/symg_test/llgo.expect +++ b/chore/_xtool/llcppsymg/_cmptest/symg_test/llgo.expect @@ -1,23 +1,23 @@ #stdout === Test Case: inireader === [{ - "mangle": "ZN9INIReaderC1EPKc", + "mangle": "_ZN9INIReaderC1EPKc", "c++": "INIReader::INIReader(const char *)", "go": "(*Reader).Init" }, { - "mangle": "ZN9INIReaderC1EPKcl", + "mangle": "_ZN9INIReaderC1EPKcl", "c++": "INIReader::INIReader(const char *, long)", "go": "(*Reader).Init__1" }, { - "mangle": "ZN9INIReaderD1Ev", + "mangle": "_ZN9INIReaderD1Ev", "c++": "INIReader::~INIReader()", "go": "(*Reader).Dispose" }, { - "mangle": "ZNK9INIReader10ParseErrorEv", + "mangle": "_ZNK9INIReader10ParseErrorEv", "c++": "INIReader::ParseError()", - "go": "(*Reader).ParseError" + "go": "(*Reader).ModifyedParseError" }, { - "mangle": "ZNK9INIReader3GetEPKcS1_S1_", + "mangle": "_ZNK9INIReader3GetEPKcS1_S1_", "c++": "INIReader::Get(const char *, const char *, const char *)", "go": "(*Reader).Get" }] diff --git a/chore/_xtool/llcppsymg/_cmptest/symg_test/symg.go b/chore/_xtool/llcppsymg/_cmptest/symg_test/symg.go index 11173662..ef90ca02 100644 --- a/chore/_xtool/llcppsymg/_cmptest/symg_test/symg.go +++ b/chore/_xtool/llcppsymg/_cmptest/symg_test/symg.go @@ -40,11 +40,11 @@ class INIReader { isCpp: true, prefixes: []string{"INI"}, dylibSymbols: []*nm.Symbol{ - {Name: "__ZN9INIReaderC1EPKc"}, - {Name: "__ZN9INIReaderC1EPKcl"}, - {Name: "__ZN9INIReaderD1Ev"}, - {Name: "__ZNK9INIReader10ParseErrorEv"}, - {Name: "__ZNK9INIReader3GetEPKcS1_S1_"}, + {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: ` [{ @@ -87,10 +87,10 @@ LUA_API void(lua_closeslot)(lua_State *L, int idx); isCpp: false, prefixes: []string{"lua_"}, dylibSymbols: []*nm.Symbol{ - {Name: "_lua_error"}, - {Name: "_lua_next"}, - {Name: "_lua_concat"}, - {Name: "_lua_stringtonumber"}, + {Name: symbol.AddSymbolPrefixUnder("lua_error", false)}, + {Name: symbol.AddSymbolPrefixUnder("lua_next", false)}, + {Name: symbol.AddSymbolPrefixUnder("lua_concat", false)}, + {Name: symbol.AddSymbolPrefixUnder("lua_stringtonumber", false)}, }, }, } diff --git a/chore/_xtool/llcppsymg/parse/parse.go b/chore/_xtool/llcppsymg/parse/parse.go index 6ebd62fe..b6d7072d 100644 --- a/chore/_xtool/llcppsymg/parse/parse.go +++ b/chore/_xtool/llcppsymg/parse/parse.go @@ -2,6 +2,7 @@ package parse import ( "errors" + "runtime" "strconv" "strings" @@ -129,12 +130,13 @@ func (p *SymbolProcessor) collectFuncInfo(cursor clang.Cursor) { symbol := cursor.Mangling() defer symbol.Dispose() - // Remove all leading underscores from C++ symbol names // On Linux, C++ symbols typically have one leading underscore // On macOS, C++ symbols may have two leading underscores - // We remove all leading underscores to handle both cases consistently + // For consistency, we remove the first leading underscore on macOS symbolName := c.GoString(symbol.CStr()) - symbolName = strings.TrimLeft(symbolName, "_") + if runtime.GOOS == "darwin" { + symbolName = strings.TrimPrefix(symbolName, "_") + } p.SymbolMap[symbolName] = &SymbolInfo{ GoName: p.genGoName(cursor), ProtoName: p.genProtoName(cursor), diff --git a/chore/_xtool/llcppsymg/symbol/symbol.go b/chore/_xtool/llcppsymg/symbol/symbol.go index ba556a6d..a6b360ae 100644 --- a/chore/_xtool/llcppsymg/symbol/symbol.go +++ b/chore/_xtool/llcppsymg/symbol/symbol.go @@ -158,7 +158,10 @@ func getPath(file string) []string { func GetCommonSymbols(dylibSymbols []*nm.Symbol, headerSymbols map[string]*parse.SymbolInfo) []*types.SymbolInfo { var commonSymbols []*types.SymbolInfo for _, dylibSym := range dylibSymbols { - symName := strings.TrimLeft(dylibSym.Name, "_") + symName := dylibSym.Name + if runtime.GOOS == "darwin" { + symName = strings.TrimPrefix(symName, "_") + } if symInfo, ok := headerSymbols[symName]; ok { symbolInfo := &types.SymbolInfo{ Mangle: symName, @@ -266,3 +269,15 @@ func GenerateAndUpdateSymbolTable(symbols []*nm.Symbol, headerInfos map[string]* 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 +}