genbindings: switch to two-phase

This commit is contained in:
mappu 2024-08-26 22:45:11 +12:00
parent f8a9a3f36e
commit f6336617a0
3 changed files with 42 additions and 15 deletions

View File

@ -1,19 +1,29 @@
# genbindings # genbindings
The `genbindings` program regenerates the qt bindings. The `genbindings` program regenerates the Qt bindings.
## Archicture design ## Architecture design
Bindings are generated as follows: Bindings are generated in two passes:
Pass 1
1. Scan input directory for header files. 1. Scan input directory for header files.
2. For each header file: 2. For each header file:
3. Run `clang --ast-dump=json` to get a JSON ast. 3. Run `clang --ast-dump=json` to get a JSON ast.
- This is somewhat slow, the results will be cached in `./cachedir` after the first run. - This is somewhat slow, the results will be cached in `./cachedir` after the first run.
- Strip all Clang AST nodes that were included from other files, to only consider the header's own definitions.
4. Convert Clang AST to our own intermediate representation. 4. Convert Clang AST to our own intermediate representation.
5. Emit CABI cpp/h pair. 5. Run some transformations on the intermediate representation.
6. Cache and collect the global state of all known class names, enum names, and typedefs.
Pass 2
1. For each intermediate-representation AST:
2. Emit "CABI" cpp/h pair.
- The CABI is a projection of Qt into plain C. The translation unit itself is C++, but the header can be used as extern c. - The CABI is a projection of Qt into plain C. The translation unit itself is C++, but the header can be used as extern c.
6. Emit Go binding file. 3. Emit Go binding file.
- The Go binding uses CGO to call into the CABI binding.
## Configuration ## Configuration
@ -21,5 +31,5 @@ It's tested to work on with Debian 12 / Qt 5.15 / Clang 14 / GCC 12.
You should check the following configuration: You should check the following configuration:
- Input directories containing Qt headers - `main.go`: Input directories containing Qt headers
- All of `exceptions.go` - `exceptions.go`: Check everything

View File

@ -194,6 +194,7 @@ type CppTypedef struct {
} }
type CppParsedHeader struct { type CppParsedHeader struct {
Filename string
Typedefs []CppTypedef Typedefs []CppTypedef
Classes []CppClass Classes []CppClass
} }

View File

@ -11,6 +11,10 @@ import (
"strings" "strings"
) )
func cacheFilePath(inputHeader string) string {
return filepath.Join("cachedir", strings.Replace(inputHeader, `/`, `__`, -1)+".json")
}
func main() { func main() {
ctx := context.Background() ctx := context.Background()
@ -78,10 +82,12 @@ func main() {
log.Printf("Removed %d file(s).", cleaned) log.Printf("Removed %d file(s).", cleaned)
} }
var processHeaders []*CppParsedHeader
for _, inputHeader := range includeFiles { for _, inputHeader := range includeFiles {
// If we have a cached clang AST, use that instead // If we have a cached clang AST, use that instead
cacheFile := filepath.Join("cachedir", strings.Replace(inputHeader, `/`, `__`, -1)+".json") cacheFile := cacheFilePath(inputHeader)
astJson, err := ioutil.ReadFile(cacheFile) astJson, err := ioutil.ReadFile(cacheFile)
var astInner []interface{} = nil var astInner []interface{} = nil
if err != nil { if err != nil {
@ -123,11 +129,23 @@ func main() {
panic(err) panic(err)
} }
parsed.Filename = inputHeader // Stash
// AST transforms on our IL // AST transforms on our IL
astTransformChildClasses(parsed) // must be first astTransformChildClasses(parsed) // must be first
astTransformOptional(parsed) astTransformOptional(parsed)
astTransformOverloads(parsed) astTransformOverloads(parsed)
processHeaders = append(processHeaders, parsed)
}
//
// PASS 2
//
for _, parsed := range processHeaders {
log.Printf("Processing %q...", parsed.Filename)
{ {
// Save the IL file for debug inspection // Save the IL file for debug inspection
jb, err := json.MarshalIndent(parsed, "", "\t") jb, err := json.MarshalIndent(parsed, "", "\t")
@ -135,7 +153,7 @@ func main() {
panic(err) panic(err)
} }
err = ioutil.WriteFile(cacheFile+".ours.json", jb, 0644) err = ioutil.WriteFile(cacheFilePath(parsed.Filename)+".ours.json", jb, 0644)
if err != nil { if err != nil {
panic(err) panic(err)
} }
@ -148,9 +166,9 @@ func main() {
} }
// Emit 3 code files from the intermediate format // Emit 3 code files from the intermediate format
outputName := filepath.Join(*outDir, "gen_"+strings.TrimSuffix(filepath.Base(inputHeader), `.h`)) outputName := filepath.Join(*outDir, "gen_"+strings.TrimSuffix(filepath.Base(parsed.Filename), `.h`))
goSrc, err := emitGo(parsed, filepath.Base(inputHeader)) goSrc, err := emitGo(parsed, filepath.Base(parsed.Filename))
if err != nil { if err != nil {
panic(err) panic(err)
} }
@ -160,7 +178,7 @@ func main() {
panic(err) panic(err)
} }
bindingCppSrc, err := emitBindingCpp(parsed, filepath.Base(inputHeader)) bindingCppSrc, err := emitBindingCpp(parsed, filepath.Base(parsed.Filename))
if err != nil { if err != nil {
panic(err) panic(err)
} }
@ -170,7 +188,7 @@ func main() {
panic(err) panic(err)
} }
bindingHSrc, err := emitBindingHeader(parsed, filepath.Base(inputHeader)) bindingHSrc, err := emitBindingHeader(parsed, filepath.Base(parsed.Filename))
if err != nil { if err != nil {
panic(err) panic(err)
} }
@ -182,8 +200,6 @@ func main() {
// Done // Done
log.Printf("Processing %q completed", inputHeader)
} }
log.Printf("Processing %d file(s) completed", len(includeFiles)) log.Printf("Processing %d file(s) completed", len(includeFiles))