From a71e911ac9d577c2e0af8de45dbf85d6aefe4880 Mon Sep 17 00:00:00 2001 From: luoliwoshang <2643523683@qq.com> Date: Tue, 15 Oct 2024 23:08:20 +0800 Subject: [PATCH 1/2] llcppsymg:multiple dylib path search --- .../_cmptest/symbol_test/llgo.expect | 33 +++-- .../llcppsymg/_cmptest/symbol_test/symbol.go | 125 +++++++++++++++++- chore/_xtool/llcppsymg/symbol/symbol.go | 56 ++++++-- 3 files changed, 185 insertions(+), 29 deletions(-) diff --git a/chore/_xtool/llcppsymg/_cmptest/symbol_test/llgo.expect b/chore/_xtool/llcppsymg/_cmptest/symbol_test/llgo.expect index 6ca2d4d70..83b9e3fa2 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 272e32241..192f2427f 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,15 +54,118 @@ 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) + + 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) - if err != nil { - fmt.Printf("Error: %v\n", err) + 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 { - fmt.Printf("Output: %v\n", result) + 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) + } + } } - fmt.Println() } + } func TestGetCommonSymbols() { fmt.Println("=== Test GetCommonSymbols ===") diff --git a/chore/_xtool/llcppsymg/symbol/symbol.go b/chore/_xtool/llcppsymg/symbol/symbol.go index a453e095c..2548bd869 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 bfbe3eed83bed8552034016d77097d8ef2af3593 Mon Sep 17 00:00:00 2001 From: luoliwoshang <2643523683@qq.com> Date: Wed, 16 Oct 2024 15:08:17 +0800 Subject: [PATCH 2/2] 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 2548bd869..9cabe9601 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 {