Skip to content

Commit

Permalink
Merge pull request #96 from luoliwoshang/llcppsymg/defaultsearch
Browse files Browse the repository at this point in the history
llcppsymg:multiple & system dylib path search
  • Loading branch information
luoliwoshang authored Oct 17, 2024
2 parents d85ba34 + bfbe3ee commit 0887e5d
Show file tree
Hide file tree
Showing 3 changed files with 228 additions and 29 deletions.
33 changes: 24 additions & 9 deletions chore/_xtool/llcppsymg/_cmptest/symbol_test/llgo.expect
Original file line number Diff line number Diff line change
@@ -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
Expand Down
125 changes: 118 additions & 7 deletions chore/_xtool/llcppsymg/_cmptest/symbol_test/symbol.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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
Expand All @@ -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",
Expand All @@ -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 ===")
Expand Down
99 changes: 86 additions & 13 deletions chore/_xtool/llcppsymg/symbol/symbol.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"os"
"path/filepath"
"runtime"
"strings"
"unsafe"

Expand All @@ -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.
Expand All @@ -48,7 +77,9 @@ 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)
defaultPaths := getSysLibPaths()
dylibPaths, err := GenDylibPaths(conf, defaultPaths)
if err != nil {
fmt.Printf("Warning: failed to generate some dylib paths: %v\n", err)
}
Expand Down Expand Up @@ -83,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 {
Expand Down

0 comments on commit 0887e5d

Please sign in to comment.