diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index e810c295..58061d97 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -126,6 +126,19 @@ jobs: llcppgtest -demos ./_llcppgtest + - name: Test demos with generated pkgs + if: startsWith(matrix.os, 'ubuntu') + run: | + # install demo's lib + sudo apt install liblua5.4-dev libsqlite3-dev libgmp-dev libgpg-error-dev zlib1g-dev -y + llcppgtest -demo ./_llcppgtest/cjson + llcppgtest -demo ./_llcppgtest/lua + llcppgtest -demo ./_llcppgtest/sqlite + llcppgtest -demo ./_llcppgtest/gmp + llcppgtest -demo ./_llcppgtest/gpgerror + llcppgtest -demo ./_llcppgtest/zlib + + - name: Upload coverage reports to Codecov uses: codecov/codecov-action@v5 with: diff --git a/_llcppgtest/cjson/llcppg.cfg b/_llcppgtest/cjson/llcppg.cfg index c80eb06e..18aa273c 100644 --- a/_llcppgtest/cjson/llcppg.cfg +++ b/_llcppgtest/cjson/llcppg.cfg @@ -3,8 +3,8 @@ "cflags": "$(pkg-config --cflags libcjson)", "libs": "$(pkg-config --libs libcjson libcjson_utils)", "include": [ - "cjson/cJSON.h", - "cjson/cJSON_Utils.h" + "cJSON.h", + "cJSON_Utils.h" ], "trimPrefixes": ["cJSON_", "cJSONUtils_"], "cplusplus": false diff --git a/_xtool/llcppsigfetch/llcppsigfetch.go b/_xtool/llcppsigfetch/llcppsigfetch.go index de0b463c..3c71ea73 100644 --- a/_xtool/llcppsigfetch/llcppsigfetch.go +++ b/_xtool/llcppsigfetch/llcppsigfetch.go @@ -28,6 +28,7 @@ import ( "github.com/goplus/llcppg/_xtool/llcppsymg/clangutils" "github.com/goplus/llcppg/_xtool/llcppsymg/config" "github.com/goplus/llcppg/_xtool/llcppsymg/config/cfgparse" + "github.com/goplus/llcppg/_xtool/llcppsymg/syspath" "github.com/goplus/llgo/c" "github.com/goplus/llgo/c/cjson" ) @@ -154,7 +155,7 @@ func runFromConfig(cfgFile string, useStdin bool, outputToFile bool, verbose boo } cflag := cfgparse.ParseCFlags(conf.CFlags) - files, notFounds, err := cflag.GenHeaderFilePaths(conf.Include) + files, notFounds, err := cflag.GenHeaderFilePaths(conf.Include, syspath.GetIncludePaths()) check(err) if verbose { diff --git a/_xtool/llcppsymg/_cmptest/config_test/config.go b/_xtool/llcppsymg/_cmptest/config_test/config.go index e095d412..9ea2f9dd 100644 --- a/_xtool/llcppsymg/_cmptest/config_test/config.go +++ b/_xtool/llcppsymg/_cmptest/config_test/config.go @@ -312,7 +312,7 @@ func TestGenHeaderFilePath() { fmt.Printf("Input files: %v\n", tc.files) cflag := cfgparse.ParseCFlags(tc.cflags) - result, notFounds, err := cflag.GenHeaderFilePaths(tc.files) + result, notFounds, err := cflag.GenHeaderFilePaths(tc.files, []string{}) if err != nil { fmt.Printf("Error: %v\n", err) diff --git a/_xtool/llcppsymg/_cmptest/syspath_test/llgo.expect b/_xtool/llcppsymg/_cmptest/syspath_test/llgo.expect new file mode 100644 index 00000000..08871253 --- /dev/null +++ b/_xtool/llcppsymg/_cmptest/syspath_test/llgo.expect @@ -0,0 +1,9 @@ +#stdout +=== TestLdOutput === +[/usr/local/lib/aarch64-linux-gnu /lib/aarch64-linux-gnu /usr/lib/aarch64-linux-gnu /usr/local/lib /lib /usr/lib /usr/aarch64-linux-gnu/lib] +=== TestClangIncOutput === +[/usr/lib/llvm-18/lib/clang/18/include /usr/local/include /usr/include/aarch64-linux-gnu /usr/include] + +#stderr + +#exit 0 diff --git a/_xtool/llcppsymg/_cmptest/syspath_test/syspath.go b/_xtool/llcppsymg/_cmptest/syspath_test/syspath.go new file mode 100644 index 00000000..55ca49fb --- /dev/null +++ b/_xtool/llcppsymg/_cmptest/syspath_test/syspath.go @@ -0,0 +1,89 @@ +package main + +import ( + "fmt" + + "github.com/goplus/llcppg/_xtool/llcppsymg/syspath" +) + +func main() { + TestLdOutput() + TestClangIncOutput() +} + +func TestLdOutput() { + fmt.Println("=== TestLdOutput ===") + res := syspath.ParseLdOutput( + `GNU ld (GNU Binutils for Ubuntu) 2.42 + Supported emulations: + aarch64linux + aarch64elf + aarch64elf32 + aarch64elf32b + aarch64elfb + armelf + armelfb + aarch64linuxb + aarch64linux32 + aarch64linux32b + armelfb_linux_eabi + armelf_linux_eabi +using internal linker script: +================================================== +/* Script for -z combreloc */ +/* Copyright (C) 2014-2024 Free Software Foundation, Inc. + Copying and distribution of this script, with or without modification, + are permitted in any medium without royalty provided the copyright + notice and this notice are preserved. */ +OUTPUT_FORMAT("elf64-littleaarch64", "elf64-bigaarch64", + "elf64-littleaarch64") +OUTPUT_ARCH(aarch64) +ENTRY(_start) +SEARCH_DIR("=/usr/local/lib/aarch64-linux-gnu"); SEARCH_DIR("=/lib/aarch64-linux-gnu"); SEARCH_DIR("=/usr/lib/aarch64-linux-gnu"); SEARCH_DIR("=/usr/local/lib"); SEARCH_DIR("=/lib"); SEARCH_DIR("=/usr/lib"); SEARCH_DIR("=/usr/aarch64-linux-gnu/lib"); +SECTIONS +{ + /* Read-only sections, merged into text segment: */ + PROVIDE (__executable_start = SEGMENT_START("text-segment", 0x400000)); . = SEGMENT_START("text-segment", 0x400000) + SIZEOF_HEADERS; + .interp : { *(.interp) } + .note.gnu.build-id : { *(.note.gnu.build-id) } + .hash : { *(.hash) } + .gnu.hash : { *(.gnu.hash) } + .dynsym : { *(.dynsym) } + .dynstr : { *(.dynstr) } +`) + fmt.Println(res) +} + +func TestClangIncOutput() { + fmt.Println("=== TestClangIncOutput ===") + res := syspath.ParseClangIncOutput( + `Ubuntu clang version 18.1.3 (1ubuntu1) +Target: aarch64-unknown-linux-gnu +Thread model: posix +InstalledDir: /usr/bin +Found candidate GCC installation: /usr/bin/../lib/gcc/aarch64-linux-gnu/13 +Selected GCC installation: /usr/bin/../lib/gcc/aarch64-linux-gnu/13 +Candidate multilib: .;@m64 +Selected multilib: .;@m64 + (in-process) + "/usr/lib/llvm-18/bin/clang" -cc1 -triple aarch64-unknown-linux-gnu -E -disable-free -clear-ast-before-backend -disable-llvm-verifier -discard-value-names -main-file-name null -mrelocation-model pic -pic-level 2 -pic-is-pie -mframe-pointer=non-leaf -fmath-errno -ffp-contract=on -fno-rounding-math -mconstructor-aliases -funwind-tables=2 -target-cpu generic -target-feature +v8a -target-feature +fp-armv8 -target-feature +neon -target-abi aapcs -debugger-tuning=gdb -fdebug-compilation-dir=/root/llcppg -v -fcoverage-compilation-dir=/root/llcppg -resource-dir /usr/lib/llvm-18/lib/clang/18 -internal-isystem /usr/lib/llvm-18/lib/clang/18/include -internal-isystem /usr/local/include -internal-isystem /usr/bin/../lib/gcc/aarch64-linux-gnu/13/../../../../aarch64-linux-gnu/include -internal-externc-isystem /usr/include/aarch64-linux-gnu -internal-externc-isystem /include -internal-externc-isystem /usr/include -ferror-limit 19 -fno-signed-char -fgnuc-version=4.2.1 -fskip-odr-check-in-gmf -fcolor-diagnostics -target-feature +outline-atomics -target-feature -fmv -faddrsig -D__GCC_HAVE_DWARF2_CFI_ASM=1 -o - -x c /dev/null +clang -cc1 version 18.1.3 based upon LLVM 18.1.3 default target aarch64-unknown-linux-gnu +ignoring nonexistent directory "/usr/bin/../lib/gcc/aarch64-linux-gnu/13/../../../../aarch64-linux-gnu/include" +ignoring nonexistent directory "/include" +#include "..." search starts here: +#include <...> search starts here: + /usr/lib/llvm-18/lib/clang/18/include + /usr/local/include + /usr/include/aarch64-linux-gnu + /usr/include +End of search list. +# 1 "/dev/null" +# 1 "" 1 +# 1 "" 3 +# 399 "" 3 +# 1 "" 1 +# 1 "" 2 +# 1 "/dev/null" 2 +`) + fmt.Println(res) +} diff --git a/_xtool/llcppsymg/config/cfgparse/parse.go b/_xtool/llcppsymg/config/cfgparse/parse.go index 6bac7430..c3c5e14c 100644 --- a/_xtool/llcppsymg/config/cfgparse/parse.go +++ b/_xtool/llcppsymg/config/cfgparse/parse.go @@ -82,13 +82,15 @@ func ParseCFlags(cflags string) *CFlags { return cf } -func (cf *CFlags) GenHeaderFilePaths(files []string) ([]string, []string, error) { +func (cf *CFlags) GenHeaderFilePaths(files []string, defaultPaths []string) ([]string, []string, error) { var foundPaths []string var notFound []string + searchPaths := append(cf.Paths, defaultPaths...) + for _, file := range files { var found bool - for _, path := range cf.Paths { + for _, path := range searchPaths { fullPath := filepath.Join(path, file) if _, err := os.Stat(fullPath); err == nil { foundPaths = append(foundPaths, fullPath) diff --git a/_xtool/llcppsymg/llcppsymg.go b/_xtool/llcppsymg/llcppsymg.go index 7a770e04..9d52a602 100644 --- a/_xtool/llcppsymg/llcppsymg.go +++ b/_xtool/llcppsymg/llcppsymg.go @@ -28,6 +28,7 @@ import ( "github.com/goplus/llcppg/_xtool/llcppsymg/dbg" "github.com/goplus/llcppg/_xtool/llcppsymg/parse" "github.com/goplus/llcppg/_xtool/llcppsymg/symbol" + "github.com/goplus/llcppg/_xtool/llcppsymg/syspath" ) func main() { @@ -79,7 +80,11 @@ func main() { check(err) cflag := cfgparse.ParseCFlags(conf.CFlags) - filepaths, notFounds, err := cflag.GenHeaderFilePaths(conf.Include) + syspaths := syspath.GetIncludePaths() + if ags.Verbose { + fmt.Println("syspaths", syspaths) + } + filepaths, notFounds, err := cflag.GenHeaderFilePaths(conf.Include, syspaths) check(err) if ags.Verbose { diff --git a/_xtool/llcppsymg/symbol/symbol.go b/_xtool/llcppsymg/symbol/symbol.go index f045ac5e..7c3f381d 100644 --- a/_xtool/llcppsymg/symbol/symbol.go +++ b/_xtool/llcppsymg/symbol/symbol.go @@ -12,6 +12,7 @@ import ( "github.com/goplus/llcppg/_xtool/llcppsymg/config/cfgparse" "github.com/goplus/llcppg/_xtool/llcppsymg/dbg" "github.com/goplus/llcppg/_xtool/llcppsymg/parse" + "github.com/goplus/llcppg/_xtool/llcppsymg/syspath" "github.com/goplus/llcppg/types" "github.com/goplus/llgo/c" "github.com/goplus/llgo/c/cjson" @@ -28,7 +29,11 @@ func ParseDylibSymbols(lib string) ([]*nm.Symbol, error) { if dbg.GetDebugSymbol() { fmt.Println("ParseDylibSymbols:from", lib) } - sysPaths := getSysLibPaths() + sysPaths := syspath.GetLibPaths() + if dbg.GetDebugSymbol() { + fmt.Println("ParseDylibSymbols:sysPaths", sysPaths) + } + lbs := cfgparse.ParseLibs(lib) if dbg.GetDebugSymbol() { fmt.Println("ParseDylibSymbols:LibConfig Parse To") @@ -60,7 +65,12 @@ func ParseDylibSymbols(lib string) ([]*nm.Symbol, error) { continue } - files, err := nm.New("").List(dylibPath) + args := []string{} + if runtime.GOOS == "linux" { + args = append(args, "-D") + } + + files, err := nm.New("").List(dylibPath, args...) if err != nil { parseErrors = append(parseErrors, fmt.Sprintf("ParseDylibSymbols:Failed to list symbols in dylib %s: %v", dylibPath, err)) continue @@ -84,61 +94,6 @@ 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" { - if dbg.GetDebugSymbol() { - fmt.Println("getSysLibPaths:find sys lib path from linux") - } - paths = []string{ - "/usr/lib", - "/usr/local/lib", - } - paths = append(paths, getPath("/etc/ld.so.conf")...) - if dbg.GetDebugSymbol() && 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 dbg.GetDebugSymbol() { - 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 dbg.GetDebugSymbol() { - fmt.Println("getPath:from", file) - } - var paths []string - content, err := os.ReadFile(file) - if err != nil { - return paths - } - lines := strings.Split(string(content), "\n") - for _, line := range lines { - line = strings.TrimSpace(line) - if line != "" && !strings.HasPrefix(line, "#") { - if file, err := os.Stat(line); err == nil && file.IsDir() { - paths = append(paths, line) - } - } - } - return paths -} - // finds the intersection of symbols from the dynamic library's symbol table and the symbols parsed from header files. // It returns a list of symbols that can be externally linked. func GetCommonSymbols(dylibSymbols []*nm.Symbol, headerSymbols map[string]*parse.SymbolInfo) []*types.SymbolInfo { diff --git a/_xtool/llcppsymg/syspath/syspath.go b/_xtool/llcppsymg/syspath/syspath.go new file mode 100644 index 00000000..53a5c780 --- /dev/null +++ b/_xtool/llcppsymg/syspath/syspath.go @@ -0,0 +1,63 @@ +package syspath + +import ( + "os/exec" + "regexp" + "runtime" + "strings" +) + +func GetLibPaths() []string { + var paths []string + if runtime.GOOS == "linux" { + //resolution from https://github.com/goplus/llcppg/commit/02307485db9269481297a4dc5e8449fffaa4f562 + cmd := exec.Command("ld", "--verbose") + output, err := cmd.Output() + if err != nil { + panic(err) + } + return ParseLdOutput(string(output)) + } + return paths +} + +// Note:this function is only use in this package +// The public function name is for llgo test +func ParseLdOutput(output string) []string { + var paths []string + matches := regexp.MustCompile(`SEARCH_DIR\("=([^"]+)"\)`).FindAllStringSubmatch(output, -1) + for _, match := range matches { + paths = append(paths, match[1]) + } + return paths +} + +func GetIncludePaths() []string { + var paths []string + if runtime.GOOS == "linux" { + cmd := exec.Command("clang", "-E", "-v", "-x", "c", "/dev/null") + output, err := cmd.CombinedOutput() + if err != nil { + panic(err) + } + return ParseClangIncOutput(string(output)) + } + return paths +} + +func ParseClangIncOutput(output string) []string { + var paths []string + start := strings.Index(output, "#include <...> search starts here:") + end := strings.Index(output, "End of search list.") + if start == -1 || end == -1 { + return paths + } + content := output[start:end] + lines := strings.Split(content, "\n") + for _, line := range lines[1:] { + if path := strings.TrimSpace(line); path != "" { + paths = append(paths, path) + } + } + return paths +} diff --git a/cmd/gogensig/convert/deps.go b/cmd/gogensig/convert/deps.go index 43c76555..f76da8ac 100644 --- a/cmd/gogensig/convert/deps.go +++ b/cmd/gogensig/convert/deps.go @@ -10,6 +10,7 @@ import ( "github.com/goplus/gogen" "github.com/goplus/llcppg/_xtool/llcppsymg/args" "github.com/goplus/llcppg/_xtool/llcppsymg/config/cfgparse" + "github.com/goplus/llcppg/_xtool/llcppsymg/syspath" cfg "github.com/goplus/llcppg/cmd/gogensig/config" "github.com/goplus/llcppg/cmd/gogensig/errs" cppgtypes "github.com/goplus/llcppg/types" @@ -164,7 +165,7 @@ func (p *PkgInfo) GetIncPaths() ([]string, []string, error) { } expandedIncFlags := env.ExpandEnv(p.CppgConf.CFlags) cflags := cfgparse.ParseCFlags(expandedIncFlags) - incPaths, notFounds, err := cflags.GenHeaderFilePaths(p.CppgConf.Include) + incPaths, notFounds, err := cflags.GenHeaderFilePaths(p.CppgConf.Include, syspath.GetIncludePaths()) p.includes = incPaths return incPaths, notFounds, err }