Files
llgo/chore/_xtool/llcppsymg/llcppsymg.go

310 lines
7.4 KiB
Go
Raw Normal View History

2024-07-25 18:46:40 +08:00
/*
* 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 (
2024-07-26 10:31:20 +08:00
"bufio"
"bytes"
"encoding/json"
"errors"
2024-07-25 18:46:40 +08:00
"fmt"
"io"
"os"
2024-07-26 10:31:20 +08:00
"os/exec"
"path/filepath"
"regexp"
"strconv"
"strings"
2024-07-26 14:37:26 +08:00
"github.com/goplus/llgo/chore/_xtool/llcppsymg/common"
"github.com/goplus/llgo/chore/llcppg/types"
2024-07-25 18:46:40 +08:00
)
func main() {
cfgFile := "llcppg.cfg"
2024-07-26 16:13:20 +08:00
if len(os.Args) > 1 {
cfgFile = os.Args[1]
}
2024-07-25 18:46:40 +08:00
var data []byte
var err error
if cfgFile == "-" {
data, err = io.ReadAll(os.Stdin)
} else {
data, err = os.ReadFile(cfgFile)
}
check(err)
2024-07-26 10:31:20 +08:00
var config types.Config
err = json.Unmarshal(data, &config)
check(err)
symbols, err := parseDylibSymbols(config.Libs)
check(err)
files, err := parseHeaderFile(config)
check(err)
2024-07-26 15:00:06 +08:00
symbolInfo := getCommonSymbols(symbols, files, config.TrimPrefixes)
2024-07-26 10:31:20 +08:00
jsonData, err := json.MarshalIndent(symbolInfo, "", " ")
check(err)
fileName := "llcppg.symb.json"
2024-07-26 16:13:20 +08:00
err = os.WriteFile(fileName, jsonData, 0644)
2024-07-26 10:31:20 +08:00
check(err)
2024-07-25 18:46:40 +08:00
2024-07-29 10:52:10 +08:00
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)
2024-07-25 18:46:40 +08:00
}
func check(err error) {
if err != nil {
panic(err)
}
}
2024-07-26 10:31:20 +08:00
func parseDylibSymbols(lib string) ([]common.CPPSymbol, error) {
dylibPath, _ := generateDylibPath(lib)
nmCmd := exec.Command("nm", "-gU", dylibPath)
nmOutput, err := nmCmd.Output()
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 := expandEnv(lib)
libPath := ""
libName := ""
for _, part := range strings.Fields(string(output)) {
if strings.HasPrefix(part, "-L") {
2024-07-26 16:13:20 +08:00
libPath = part[2:]
2024-07-26 10:31:20 +08:00
} else if strings.HasPrefix(part, "-l") {
2024-07-26 16:13:20 +08:00
libName = part[2:]
2024-07-26 10:31:20 +08:00
}
}
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) []common.CPPSymbol {
scanner := bufio.NewScanner(bytes.NewReader(output))
var symbols []common.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
if strings.HasPrefix(symbolName, "_") {
symbolName = symbolName[1:]
}
symbols = append(symbols, common.CPPSymbol{
Symbol: symbolName,
Type: fields[1],
Name: fields[2],
})
}
return symbols
}
func decodeSymbolName(symbolName string) (string, error) {
cppfiltCmd := exec.Command("c++filt", symbolName)
cppfiltOutput, err := cppfiltCmd.Output()
if err != nil {
return "", errors.New("failed to execute c++filt command")
}
decodedName := strings.TrimSpace(string(cppfiltOutput))
decodedName = strings.ReplaceAll(decodedName, "std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const", "std::string")
return decodedName, nil
}
// parseHeaderFile
func parseHeaderFile(config types.Config) ([]common.ASTInformation, error) {
files := generateHeaderFilePath(config.CFlags, config.Include)
2024-07-26 14:37:26 +08:00
fmt.Println(files)
2024-07-26 10:31:20 +08:00
headerFileCmd := exec.Command("llcppinfofetch", files...)
2024-07-26 14:37:26 +08:00
fmt.Println("Executing command:", headerFileCmd.String())
2024-07-26 10:31:20 +08:00
headerFileOutput, err := headerFileCmd.Output()
if err != nil {
return nil, errors.New("failed to execute header file command")
}
2024-07-26 14:37:26 +08:00
fmt.Println("headerFileOutput:", string(headerFileOutput), len(headerFileOutput))
2024-07-26 10:31:20 +08:00
t := make([]common.ASTInformation, 0)
err = json.Unmarshal(headerFileOutput, &t)
if err != nil {
return nil, err
}
return t, nil
}
func generateHeaderFilePath(cflags string, files []string) []string {
prefixPath := expandEnv(cflags)
if strings.HasPrefix(prefixPath, "-I") {
prefixPath = prefixPath[2:]
}
2024-07-26 16:13:20 +08:00
2024-07-26 10:31:20 +08:00
prefixPath = strings.TrimSpace(prefixPath)
var includePaths []string
for _, file := range files {
includePaths = append(includePaths, filepath.Join(prefixPath, "/"+file))
}
return includePaths
}
2024-07-26 15:00:06 +08:00
func getCommonSymbols(dylibSymbols []common.CPPSymbol, astInfoList []common.ASTInformation, prefix []string) []common.SymbolInfo {
2024-07-26 10:31:20 +08:00
var commonSymbols []common.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 := common.SymbolInfo{
Mangle: dylibSym.Symbol,
CPP: cppName,
2024-07-26 15:00:06 +08:00
Go: generateMangle(astInfo, functionNameMap[cppName], prefix),
2024-07-26 10:31:20 +08:00
}
commonSymbols = append(commonSymbols, symbolInfo)
break
}
}
}
return commonSymbols
}
func generateCPPName(astInfo common.ASTInformation) string {
cppName := astInfo.Name
if astInfo.Class != "" {
cppName = astInfo.Class + "::" + astInfo.Name
}
return cppName
}
2024-07-26 15:00:06 +08:00
func generateMangle(astInfo common.ASTInformation, count int, prefixes []string) string {
astInfo.Class = removePrefix(astInfo.Class, prefixes)
astInfo.Name = removePrefix(astInfo.Name, prefixes)
2024-07-26 10:31:20 +08:00
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 {
2024-07-26 14:37:26 +08:00
res = "(*" + astInfo.Class + ")." + astInfo.Name
if count > 1 {
res += "__" + strconv.Itoa(count-1)
}
2024-07-26 10:31:20 +08:00
}
} else {
res = astInfo.Name
2024-07-26 14:37:26 +08:00
if count > 1 {
2024-07-26 10:31:20 +08:00
res += "__" + strconv.Itoa(count-1)
}
}
return res
}
2024-07-26 15:00:06 +08:00
func removePrefix(str string, prefixes []string) string {
for _, prefix := range prefixes {
if strings.HasPrefix(str, prefix) {
return strings.TrimPrefix(str, prefix)
}
}
return str
}
2024-07-26 10:31:20 +08:00
var (
reSubcmd = regexp.MustCompile(`\$\([^)]+\)`)
reFlag = regexp.MustCompile(`[^ \t\n]+`)
)
func expandEnv(s string) string {
return expandEnvWithCmd(s)
}
func expandEnvWithCmd(s string) string {
expanded := reSubcmd.ReplaceAllStringFunc(s, func(m string) string {
subcmd := strings.TrimSpace(s[2 : len(s)-1])
args := parseSubcmd(subcmd)
cmd := args[0]
if cmd != "pkg-config" && cmd != "llvm-config" {
fmt.Fprintf(os.Stderr, "expand cmd only support pkg-config and llvm-config: '%s'\n", subcmd)
return ""
}
var out []byte
var err error
out, err = exec.Command(cmd, args[1:]...).Output()
if err != nil {
// TODO(kindy): log in verbose mode
return ""
}
return string(out)
})
return os.Expand(expanded, os.Getenv)
}
func parseSubcmd(s string) []string {
return reFlag.FindAllString(s, -1)
}