/* * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package main import ( "bufio" "bytes" "encoding/json" "errors" "fmt" "io" "os" "os/exec" "path/filepath" "strconv" "strings" "github.com/goplus/llgo/c" "github.com/goplus/llgo/c/cjson" "github.com/goplus/llgo/chore/llcppg/types" "github.com/goplus/llgo/cpp/llvm" ) func main() { cfgFile := "llcppg.cfg" if len(os.Args) > 1 { cfgFile = os.Args[1] } var data []byte var err error if cfgFile == "-" { data, err = io.ReadAll(os.Stdin) } else { data, err = os.ReadFile(cfgFile) } check(err) config, err := getConf(data) if err != nil { fmt.Fprintln(os.Stderr, "Failed to parse config file:", cfgFile) } symbols, err := parseDylibSymbols(config.Libs) check(err) files, err := parseHeaderFile(config) check(err) symbolInfo := getCommonSymbols(symbols, files, config.TrimPrefixes) jsonData, err := json.MarshalIndent(symbolInfo, "", " ") check(err) fileName := "llcppg.symb.json" err = os.WriteFile(fileName, jsonData, 0644) check(err) absJSONPath, err := filepath.Abs(fileName) check(err) config.JSONPath = absJSONPath updatedCfgData, err := json.MarshalIndent(config, "", " ") check(err) err = os.WriteFile(cfgFile, updatedCfgData, 0644) check(err) } func check(err error) { if err != nil { panic(err) } } func getConf(data []byte) (config types.Config, err error) { conf := cjson.ParseBytes(data) defer conf.Delete() if conf == nil { return config, errors.New("failed to execute nm command") } config.Name = c.GoString(conf.GetItem("name").GetStringValue()) config.CFlags = c.GoString(conf.GetItem("cflags").GetStringValue()) config.Libs = c.GoString(conf.GetItem("libs").GetStringValue()) config.Include = make([]string, conf.GetItem("include").GetArraySize()) for i := range config.Include { config.Include[i] = c.GoString(conf.GetItem("include").GetArrayItem(c.Int(i)).GetStringValue()) } config.TrimPrefixes = make([]string, conf.GetItem("trimPrefixes").GetArraySize()) for i := range config.TrimPrefixes { config.TrimPrefixes[i] = c.GoString(conf.GetItem("trimPrefixes").GetArrayItem(c.Int(i)).GetStringValue()) } return } func parseDylibSymbols(lib string) ([]types.CPPSymbol, error) { dylibPath, _ := generateDylibPath(lib) nmCmd := exec.Command("nm", "-gU", dylibPath) nmOutput, err := nmCmd.Output() // maybe lock if err != nil { return nil, errors.New("failed to execute nm command") } symbols := parseNmOutput(nmOutput) for i, sym := range symbols { decodedName, err := decodeSymbolName(sym.Name) if err != nil { return nil, err } symbols[i].Name = decodedName } return symbols, nil } func generateDylibPath(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 parseNmOutput(output []byte) []types.CPPSymbol { scanner := bufio.NewScanner(bytes.NewReader(output)) var symbols []types.CPPSymbol for scanner.Scan() { line := scanner.Text() fields := strings.Fields(line) if len(fields) < 3 { continue } symbolName := fields[2] // Check if the symbol name starts with an underscore and remove it if present symbolName = strings.TrimPrefix(symbolName, "_") symbols = append(symbols, types.CPPSymbol{ Symbol: symbolName, Type: fields[1], Name: fields[2], }) } return symbols } func decodeSymbolName(symbolName string) (string, error) { llvm.ItaniumDemangle(symbolName, true) demangleName := c.GoString(llvm.ItaniumDemangle(symbolName, true)) decodedName := strings.TrimSpace(string(demangleName)) decodedName = strings.ReplaceAll(decodedName, "std::__1::basic_string, std::__1::allocator > const", "std::string") return decodedName, nil } func parseHeaderFile(config types.Config) ([]types.ASTInformation, error) { files := generateHeaderFilePath(config.CFlags, config.Include) fmt.Println(files) headerFileCmd := exec.Command("llcppinfofetch", files...) fmt.Println("Executing command:", headerFileCmd.String()) headerFileOutput, err := headerFileCmd.Output() if err != nil { return nil, errors.New("failed to execute header file command") } fmt.Println("headerFileOutput:", string(headerFileOutput), len(headerFileOutput)) t := make([]types.ASTInformation, 0) err = json.Unmarshal(headerFileOutput, &t) if err != nil { return nil, err } return t, nil } func generateHeaderFilePath(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 []types.CPPSymbol, astInfoList []types.ASTInformation, prefix []string) []types.SymbolInfo { var commonSymbols []types.SymbolInfo functionNameMap := make(map[string]int) for _, astInfo := range astInfoList { for _, dylibSym := range dylibSymbols { if dylibSym.Symbol == astInfo.Symbol { cppName := generateCPPName(astInfo) functionNameMap[cppName]++ symbolInfo := types.SymbolInfo{ Mangle: dylibSym.Symbol, CPP: cppName, Go: generateMangle(astInfo, functionNameMap[cppName], prefix), } commonSymbols = append(commonSymbols, symbolInfo) break } } } return commonSymbols } func generateCPPName(astInfo types.ASTInformation) string { cppName := astInfo.Name if astInfo.Class != "" { cppName = astInfo.Class + "::" + astInfo.Name } return cppName } func generateMangle(astInfo types.ASTInformation, count int, prefixes []string) string { astInfo.Class = removePrefix(astInfo.Class, prefixes) astInfo.Name = removePrefix(astInfo.Name, prefixes) res := "" if astInfo.Class != "" { if astInfo.Class == astInfo.Name { res = "(*" + astInfo.Class + ")." + "Init" if count > 1 { res += "__" + strconv.Itoa(count-1) } } else if astInfo.Name == "~"+astInfo.Class { res = "(*" + astInfo.Class + ")." + "Dispose" if count > 1 { res += "__" + strconv.Itoa(count-1) } } else { res = "(*" + astInfo.Class + ")." + astInfo.Name if count > 1 { res += "__" + strconv.Itoa(count-1) } } } else { res = astInfo.Name if count > 1 { res += "__" + strconv.Itoa(count-1) } } return res } func removePrefix(str string, prefixes []string) string { for _, prefix := range prefixes { if strings.HasPrefix(str, prefix) { return strings.TrimPrefix(str, prefix) } } return str }