Skip to content

Commit 6855c85

Browse files
committed
fix conflict
2 parents 2467df6 + 4580427 commit 6855c85

File tree

5 files changed

+221
-122
lines changed

5 files changed

+221
-122
lines changed

.github/workflows/end2end.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,8 @@ jobs:
5252
- name: Test End2End
5353
working-directory: _cmptest
5454
run: |
55-
go test -v .
55+
# increase the test timeout to avoid conan installation timeout occasionally
56+
go test -v -timeout 30m .
5657
5758
- name: Upload Logs to Artifacts
5859
uses: actions/upload-artifact@v4

_cmptest/llcppgend_test.go

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,6 @@ const llcppgGoVersion = "1.20.14"
2020
var (
2121
// avoid conan install race condition
2222
conanInstallMutex sync.Mutex
23-
// avoid llgo run race condition
24-
llgoRunMutex sync.Mutex
2523
)
2624

2725
type testCase struct {
@@ -234,9 +232,10 @@ func runDemos(t *testing.T, logFile *os.File, demosPath string, pkgname, pkgpath
234232
t.Fatal(err)
235233
}
236234

237-
// only can lock out of loop,will got ld64.lld: error: undefined symbol: math.Float32bits
238-
llgoRunMutex.Lock()
239-
defer llgoRunMutex.Unlock()
235+
llgoRunTempDir, err := os.MkdirTemp("", "llgo-run")
236+
if err != nil {
237+
t.Fatal(err)
238+
}
240239

241240
for _, demo := range demos {
242241
if !demo.IsDir() {
@@ -246,6 +245,8 @@ func runDemos(t *testing.T, logFile *os.File, demosPath string, pkgname, pkgpath
246245
demoCmd := command(logFile, demoPath, "llgo", "run", ".")
247246
demoCmd.Env = append(demoCmd.Env, llgoEnv()...)
248247
demoCmd.Env = append(demoCmd.Env, pcPathEnv(pcPath)...)
248+
demoCmd.Env = append(demoCmd.Env, tempDirEnv(llgoRunTempDir)...)
249+
249250
err = demoCmd.Run()
250251
if err != nil {
251252
t.Fatal(err)
@@ -280,6 +281,16 @@ func goVerEnv() string {
280281
return fmt.Sprintf("GOTOOLCHAIN=go%s", llcppgGoVersion)
281282
}
282283

284+
func tempDirEnv(tempDir string) []string {
285+
return []string{
286+
fmt.Sprintf("TMPDIR=%s", tempDir),
287+
fmt.Sprintf("TEMP=%s", tempDir),
288+
fmt.Sprintf("TMP=%s", tempDir),
289+
fmt.Sprintf("GOTMPDIR=%s", tempDir),
290+
fmt.Sprintf("GOCACHE=%s", tempDir),
291+
}
292+
}
293+
283294
func copyFile(src, dst string) error {
284295
srcFile, err := os.Open(src)
285296
if err != nil {

cmd/llcppgtest/demo/demo.go

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,6 @@ import (
1313
llcppg "github.com/goplus/llcppg/config"
1414
)
1515

16-
var llgoRunMu sync.Mutex
17-
1816
var mkdirTempLazily = sync.OnceValue(func() string {
1917
if env := os.Getenv("LLCPPG_TEST_LOG_DIR"); env != "" {
2018
return env
@@ -137,11 +135,6 @@ func RunGenPkgDemo(demoRoot string, confDir string) error {
137135
return fmt.Errorf("%s: go fmt failed in %s: %w", demoPkgName, genPkgDir, err)
138136
}
139137

140-
if err = runCommand(tempLog, genPkgDir, "llgo", "build", "."); err != nil {
141-
return fmt.Errorf("%s: llgo build failed in %s: %w", demoPkgName, genPkgDir, err)
142-
}
143-
fmt.Printf("%s: llgo build success\n", demoPkgName)
144-
145138
demosPath := filepath.Join(demoRoot, "demo")
146139
// init mods to test package,because the demo is dependent on the gen pkg
147140
if err = runCommand(tempLog, demoRoot, "go", "mod", "init", "demo"); err != nil {
@@ -164,15 +157,22 @@ func RunGenPkgDemo(demoRoot string, confDir string) error {
164157
return fmt.Errorf("%s: failed to read demo directory: %v", demoPkgName, err)
165158
}
166159

167-
// start to test demos via llgo run
168-
// to avoid potential racy, we must grab the lock
169-
llgoRunMu.Lock()
170-
defer llgoRunMu.Unlock()
160+
llgoRunTempDir, err := os.MkdirTemp("", "llgo-run")
161+
if err != nil {
162+
return err
163+
}
171164

172165
for _, demo := range demos {
173166
if demo.IsDir() {
174167
fmt.Printf("%s: Running demo: %s\n", demoPkgName, demo.Name())
175-
if demoErr := runCommand(tempLog, filepath.Join(demosPath, demo.Name()), "llgo", "run", "."); demoErr != nil {
168+
169+
// avoid racy
170+
if demoErr := runCommandWithTempDir(
171+
tempLog,
172+
filepath.Join(demosPath, demo.Name()),
173+
llgoRunTempDir,
174+
"llgo", "run", "-v", ".",
175+
); demoErr != nil {
176176
return fmt.Errorf("%s: failed to run demo: %s: %w", demoPkgName, demo.Name(), demoErr)
177177
}
178178
}
@@ -255,3 +255,17 @@ func runCommand(logFile *os.File, dir, command string, args ...string) error {
255255
cmd.Stderr = logFile
256256
return cmd.Run()
257257
}
258+
259+
func runCommandWithTempDir(logFile *os.File, dir, tempDir string, command string, args ...string) error {
260+
cmd := exec.Command(command, args...)
261+
cmd.Dir = dir
262+
cmd.Stdout = logFile
263+
cmd.Stderr = logFile
264+
cmd.Env = append(os.Environ(), fmt.Sprintf("TMPDIR=%s", tempDir))
265+
cmd.Env = append(cmd.Env, fmt.Sprintf("TEMP=%s", tempDir))
266+
cmd.Env = append(cmd.Env, fmt.Sprintf("TMP=%s", tempDir))
267+
cmd.Env = append(cmd.Env, fmt.Sprintf("GOTMPDIR=%s", tempDir))
268+
cmd.Env = append(cmd.Env, fmt.Sprintf("GOCACHE=%s", tempDir))
269+
270+
return cmd.Run()
271+
}

doc/en/dev/llcppg.md

Lines changed: 177 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ typedef int (*CallBack)(void *L);
5555
type CallBack func(c.Pointer) c.Int
5656
```
5757

58-
For function pointer types referenced in function signatures, the type is replaced with the converted Go function type.
58+
For function pointer types referenced in function signatures & struct fields, the type is replaced with the converted Go function type.
5959

6060
```c
6161
void exec(void *L, CallBack cb);
@@ -65,6 +65,17 @@ void exec(void *L, CallBack cb);
6565
func Exec(L c.Pointer, cb CallBack)
6666
```
6767

68+
```c
69+
typedef struct Stream {
70+
CallBack cb;
71+
} Stream;
72+
```
73+
```go
74+
type Stream struct {
75+
Cb CallBack
76+
}
77+
```
78+
6879
For cases where a parameter in a function signature is an anonymous function pointer (meaning it does not reference a pre-defined function pointer type), it is mapped to the corresponding Go function type.
6980

7081
```c
@@ -83,21 +94,9 @@ func (recv_ *Sqlite3) Exec(sql *c.Char, callback func(c.Pointer, c.Int, **c.Char
8394
}
8495
```
8596

86-
For struct fields that are named function pointer types, the field type is replaced with a `c.Pointer` for description.
87-
88-
```c
89-
typedef struct Stream {
90-
CallBack cb;
91-
} Stream;
92-
```
93-
```go
94-
type Stream struct {
95-
Cb c.Pointer
96-
}
97-
```
98-
Due to the characteristics of LLGo, an anonymous function type cannot be directly declared as a Field Type. So to preserve as much information as possible, we need to use a `c.Pointer` to describe the field type.
97+
Due to the characteristics of LLGo, an anonymous function type cannot be directly declared as a Field Type. So to preserve as much information as possible, we need to use a autogenerated function type and reference it to describe the field type.
9998

100-
For anonymous function pointer types, llgo will build a public function type for them and reference it,and ensure that the anonymous type is unique, the naming rule for the corresponding type will be:
99+
llcppg will build a public function type for them and reference it,and ensure that the anonymous func type is unique, the naming rule for the corresponding type will be:
101100
```
102101
LLGO_<namespaces>_<typename>_<nested_field_typename>_<fieldname>
103102
```
@@ -206,7 +205,6 @@ char matrix[3][4]; // In function parameter becomes **c.Char
206205
char field[3][4]; // In struct field becomes [3][4]c.Char
207206
```
208207

209-
210208
#### Name Mapping Rules
211209

212210
The llcppg system converts C/C++ type names to Go-compatible identifiers following specific transformation rules. These rules ensure generated Go code follows Go naming conventions while maintaining clarity and avoiding conflicts.
@@ -506,4 +504,166 @@ linux amd64 `t1_linux_amd64.go` `t2_linux_amd64.go`
506504
```go
507505
// +build linux,amd64
508506
package xxx
509-
```
507+
```
508+
509+
## Usage
510+
511+
```sh
512+
llcppg [config-file]
513+
```
514+
515+
If `config-file` is not specified, a `llcppg.cfg` file is used in current directory. The configuration file format is as follows:
516+
517+
```json
518+
{
519+
"name": "inih",
520+
"cflags": "$(pkg-config --cflags inireader)",
521+
"include": [
522+
"INIReader.h",
523+
"AnotherHeaderFile.h"
524+
],
525+
"libs": "$(pkg-config --libs inireader)",
526+
"trimPrefixes": ["Ini", "INI"],
527+
"cplusplus":true,
528+
"deps":["c","github.com/..../third"],
529+
"mix":false
530+
}
531+
```
532+
533+
## Process Steps
534+
535+
The llcppg tool orchestrates a three-stage pipeline that automatically generates Go bindings for C/C++ libraries by coordinating symbol table generation, signature extraction, and Go code generation components.
536+
537+
1. llcppsymg: Generate symbol table for a C/C++ library
538+
2. llcppsigfetch: Fetch information of C/C++ symbols
539+
3. gogensig: Generate a Go package by information of symbols
540+
541+
### llcppsymg
542+
543+
```sh
544+
llcppsymg config-file
545+
llcppsymg - # read config from stdin
546+
```
547+
548+
llcppsymg is the symbol table generator in the llcppg toolchain, responsible for analyzing C/C++ dynamic libraries and header files to generate symbol mapping tables. Its main functions are:
549+
550+
1. Parse dynamic library symbols: Extract exported symbols from libraries using the nm tool
551+
2. Parse header file declarations: Analyze C/C++ header files using libclang for function declarations
552+
3. Find intersection: Match library symbols with header declarations and then generate symbol table named `llcppg.symb.json`.
553+
554+
#### Symbol Table
555+
556+
This symbol table determines whether the function appears in the generated Go code、its actual name and if it is a method. Its file format is as follows:
557+
558+
```json
559+
[
560+
{
561+
"mangle": "cJSON_Delete",
562+
"c++": "cJSON_Delete(cJSON *)",
563+
"go": "(*CJSON).Delete"
564+
},
565+
]
566+
```
567+
568+
* mangle: mangled name of function
569+
* c++: C/C++ function prototype declaration string
570+
* go: corresponding Go function or method name, during the process, llcppg will automatically check if the current function can be a method
571+
1. When go is "-", the function is ignored (not generated)
572+
2. When go is a valid function name, the function name will be named as the mangle
573+
3. When go is `(*Type).MethodName` or `Type.MethodName`, the function will be generated as a method with Receiver as Type/*Type, and Name as MethodName
574+
575+
#### Custom Symbol Table generation
576+
577+
Specify function mapping behavior in `llcppg.cfg` by config the `symMap` field:
578+
```json
579+
{
580+
"symMap":{
581+
"mangle":"<goFuncName> | <.goMethodName> | -"
582+
}
583+
}
584+
```
585+
`mangle` is the symbol name of the function. For the value of `mangle`, you can customize it as:
586+
1. `goFuncName` - generates a regular function named `goFuncName`
587+
2. `.goMethodName` - generates a method named `goMethodName` (if it doesn't meet the rules for generating a method, it will be generated as a regular function)
588+
3. `-` - completely ignore this function
589+
590+
For example, to convert `(*CJSON).PrintUnformatted` from a method to a function, you can use follow config:
591+
592+
```json
593+
{
594+
"symMap":{
595+
"cJSON_PrintUnformatted":"PrintUnformatted"
596+
}
597+
}
598+
```
599+
and the `llcppg.symb.json` will be:
600+
```json
601+
[
602+
{
603+
"mangle": "cJSON_PrintUnformatted",
604+
"c++": "cJSON_PrintUnformatted(cJSON *)",
605+
"go": "PrintUnformatted"
606+
}
607+
]
608+
```
609+
610+
### llcppsigfetch
611+
612+
llcppsigfetch is a tool that extracts type information and function signatures from C/C++ header files. It uses Clang & Libclang to parse C/C++ header files and outputs a JSON-formatted package information structure.
613+
614+
```sh
615+
llcppsigfetch config-file
616+
llcppsigfetch - # read config from stdin
617+
```
618+
619+
* Preprocesses C/C++ header files
620+
* Creates translation units using libclang and traverses the preprocessed header file to extract ast info.
621+
622+
#### Output:
623+
624+
The output is a `pkg-info` structure that contains comprehensive package information needed for Go code generation. This `pkg-info` consists of two main components:
625+
626+
* File: Contains the AST with decls, includes, and macros.
627+
* FileMap: Maps file paths to file types, where FileType indicates file classification (interface, implementation, or third-party files)
628+
629+
```json
630+
{
631+
"File": {
632+
"decls": [],
633+
"includes": [],
634+
"macros": []
635+
},
636+
"FileMap": {
637+
"usr/include/sys/_types/_rsize_t.h": {
638+
"FileType": 3
639+
},
640+
"/opt/homebrew/include/lua/lua.h": {
641+
"FileType": 1
642+
},
643+
"/opt/homebrew/include/lua/luaconf.h": {
644+
"FileType": 2
645+
}
646+
}
647+
}
648+
```
649+
650+
### gogensig
651+
652+
gogensig is the final component in the pipeline, responsible for converting C/C++ type declarations and function signatures into Go code. It reads the `pkg-info` structure generated by llcppsigfetch.
653+
654+
```sh
655+
gogensig pkg-info-file
656+
gogensig - # read pkg-info-file from stdin
657+
```
658+
659+
#### Function Generation
660+
During execution, gogensig only generates functions whose corresponding mangle exists in llcppg.symb.json, determining whether to generate functions/methods with specified Go names by parsing the go field corresponding to the mangle.
661+
662+
1. Regular function format: "FunctionName"
663+
* Generates regular functions, using `//go:linkname` annotation
664+
2. Pointer receiver method format: "(*TypeName).MethodName"
665+
* Generates methods with pointer receivers, using `// llgo:link` annotation
666+
3. Value receiver method format: "TypeName.MethodName"
667+
* Generates methods with value receivers, using `// llgo:link` annotation
668+
4. Ignore function format: "-"
669+
* Completely ignores the function, generates no code

0 commit comments

Comments
 (0)