2024-08-06 01:03:23 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2024-08-07 06:51:30 +00:00
|
|
|
"encoding/json"
|
2024-08-06 01:03:23 +00:00
|
|
|
"flag"
|
2024-08-06 02:29:12 +00:00
|
|
|
"io/ioutil"
|
2024-08-06 01:03:23 +00:00
|
|
|
"log"
|
2024-08-19 07:14:47 +00:00
|
|
|
"os"
|
2024-08-06 02:29:12 +00:00
|
|
|
"path/filepath"
|
2024-08-06 01:03:23 +00:00
|
|
|
"strings"
|
2024-09-11 05:44:36 +00:00
|
|
|
"time"
|
2024-08-06 01:03:23 +00:00
|
|
|
)
|
|
|
|
|
2024-08-26 10:45:11 +00:00
|
|
|
func cacheFilePath(inputHeader string) string {
|
|
|
|
return filepath.Join("cachedir", strings.Replace(inputHeader, `/`, `__`, -1)+".json")
|
|
|
|
}
|
|
|
|
|
2024-08-06 01:03:23 +00:00
|
|
|
func main() {
|
|
|
|
ctx := context.Background()
|
|
|
|
|
|
|
|
clang := flag.String("clang", "clang", "Custom path to clang")
|
|
|
|
cflags := flag.String("cflags", `-DQT_WIDGETS_LIB -I/usr/include/x86_64-linux-gnu/qt5/QtWidgets -I/usr/include/x86_64-linux-gnu/qt5 -I/usr/include/x86_64-linux-gnu/qt5/QtCore -DQT_GUI_LIB -I/usr/include/x86_64-linux-gnu/qt5/QtGui -DQT_CORE_LIB`, "Cflags to pass to clang (e.g. `pkg-config --cflags Qt5Widgets`)")
|
2024-08-10 01:32:43 +00:00
|
|
|
outDir := flag.String("outdir", "../../qt", "Output directory for generated gen_** files")
|
2024-08-06 01:03:23 +00:00
|
|
|
|
|
|
|
flag.Parse()
|
|
|
|
|
2024-08-19 07:14:47 +00:00
|
|
|
var includeFiles []string
|
|
|
|
|
|
|
|
for _, srcDir := range []string{
|
|
|
|
"/usr/include/x86_64-linux-gnu/qt5/QtCore",
|
|
|
|
"/usr/include/x86_64-linux-gnu/qt5/QtGui",
|
|
|
|
"/usr/include/x86_64-linux-gnu/qt5/QtWidgets",
|
|
|
|
} {
|
|
|
|
content, err := os.ReadDir(srcDir)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, includeFile := range content {
|
|
|
|
if includeFile.IsDir() {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if !strings.HasSuffix(includeFile.Name(), `.h`) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
fullPath := filepath.Join(srcDir, includeFile.Name())
|
|
|
|
if !AllowHeader(fullPath) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
includeFiles = append(includeFiles, fullPath)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
log.Printf("Found %d header files to process.", len(includeFiles))
|
|
|
|
|
|
|
|
{
|
|
|
|
log.Printf("Cleaning up output directory %q...", *outDir)
|
|
|
|
|
|
|
|
existing, err := os.ReadDir(*outDir)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
cleaned := 0
|
|
|
|
for _, e := range existing {
|
|
|
|
if e.IsDir() {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if !strings.HasPrefix(e.Name(), `gen_`) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
// One of ours, clean up
|
|
|
|
err := os.Remove(filepath.Join(*outDir, e.Name()))
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("WARNING: Failed to remove existing file %q", e.Name())
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
cleaned++
|
|
|
|
}
|
|
|
|
|
|
|
|
log.Printf("Removed %d file(s).", cleaned)
|
2024-08-06 01:03:23 +00:00
|
|
|
}
|
|
|
|
|
2024-08-26 10:45:11 +00:00
|
|
|
var processHeaders []*CppParsedHeader
|
2024-09-16 07:03:45 +00:00
|
|
|
atr := astTransformRedundant{
|
|
|
|
preserve: make(map[string]*CppEnum),
|
|
|
|
}
|
2024-08-26 10:45:11 +00:00
|
|
|
|
2024-09-19 07:39:05 +00:00
|
|
|
InsertTypedefs()
|
|
|
|
|
2024-08-14 05:43:54 +00:00
|
|
|
for _, inputHeader := range includeFiles {
|
|
|
|
|
|
|
|
// If we have a cached clang AST, use that instead
|
2024-08-26 10:45:11 +00:00
|
|
|
cacheFile := cacheFilePath(inputHeader)
|
2024-08-14 05:43:54 +00:00
|
|
|
astJson, err := ioutil.ReadFile(cacheFile)
|
|
|
|
var astInner []interface{} = nil
|
2024-08-07 06:51:30 +00:00
|
|
|
if err != nil {
|
2024-08-14 05:43:54 +00:00
|
|
|
|
|
|
|
// Nonexistent cache file, regenerate from clang
|
|
|
|
log.Printf("No AST cache for file %q, running clang...", filepath.Base(inputHeader))
|
|
|
|
|
|
|
|
// Parse the file
|
2024-09-11 05:44:36 +00:00
|
|
|
// This seems to intermittently fail, so allow retrying
|
|
|
|
nextRetry:
|
|
|
|
for retryCt := 0; retryCt < 5; retryCt++ {
|
|
|
|
astInner, err = clangExec(ctx, *clang, inputHeader, strings.Fields(*cflags))
|
|
|
|
if err != nil {
|
|
|
|
// Log and continue with next retry
|
|
|
|
log.Printf("WARNING: Clang execution failed: %v", err)
|
|
|
|
time.Sleep(3 * time.Second)
|
|
|
|
log.Printf("Retrying...")
|
|
|
|
|
|
|
|
} else { // err == nil
|
|
|
|
break nextRetry
|
|
|
|
}
|
|
|
|
}
|
2024-08-14 05:43:54 +00:00
|
|
|
if err != nil {
|
2024-09-11 05:44:36 +00:00
|
|
|
panic("Clang execution failed after 5x retries")
|
2024-08-14 05:43:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Write to cache
|
|
|
|
jb, err := json.MarshalIndent(astInner, "", "\t")
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
err = ioutil.WriteFile(cacheFile, jb, 0644)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
} else {
|
|
|
|
log.Printf("Reused cache AST for file %q", filepath.Base(inputHeader))
|
|
|
|
|
|
|
|
// Json decode
|
|
|
|
err = json.Unmarshal(astJson, &astInner)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
2024-08-07 06:51:30 +00:00
|
|
|
}
|
2024-08-10 01:32:43 +00:00
|
|
|
|
2024-08-14 05:43:54 +00:00
|
|
|
// Convert it to our intermediate format
|
2024-08-27 07:12:08 +00:00
|
|
|
parsed, err := parseHeader(astInner, "")
|
2024-08-10 01:32:43 +00:00
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
2024-08-07 06:51:30 +00:00
|
|
|
|
2024-08-26 10:45:11 +00:00
|
|
|
parsed.Filename = inputHeader // Stash
|
|
|
|
|
2024-08-15 07:51:49 +00:00
|
|
|
// AST transforms on our IL
|
2024-08-25 07:08:28 +00:00
|
|
|
astTransformChildClasses(parsed) // must be first
|
2024-08-15 07:51:49 +00:00
|
|
|
astTransformOptional(parsed)
|
|
|
|
astTransformOverloads(parsed)
|
2024-09-16 07:03:45 +00:00
|
|
|
atr.Process(parsed)
|
2024-08-15 07:51:49 +00:00
|
|
|
|
2024-08-26 10:48:37 +00:00
|
|
|
// Update global state tracker (AFTER astTransformChildClasses)
|
|
|
|
// Currently, this is only used for inner classes
|
|
|
|
for _, c := range parsed.Classes {
|
|
|
|
KnownClassnames[c.ClassName] = struct{}{}
|
|
|
|
}
|
2024-08-26 10:49:33 +00:00
|
|
|
for _, td := range parsed.Typedefs {
|
|
|
|
KnownTypedefs[td.Alias] = td // copy
|
|
|
|
}
|
2024-09-04 06:54:10 +00:00
|
|
|
for _, en := range parsed.Enums {
|
|
|
|
KnownEnums[en.EnumName] = en // copy
|
|
|
|
}
|
2024-08-26 10:49:33 +00:00
|
|
|
|
2024-08-26 10:45:11 +00:00
|
|
|
processHeaders = append(processHeaders, parsed)
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// PASS 2
|
|
|
|
//
|
|
|
|
|
|
|
|
for _, parsed := range processHeaders {
|
|
|
|
|
|
|
|
log.Printf("Processing %q...", parsed.Filename)
|
2024-08-26 10:49:33 +00:00
|
|
|
|
|
|
|
// More AST transforms on our IL
|
|
|
|
astTransformTypedefs(parsed)
|
2024-08-27 06:44:10 +00:00
|
|
|
astTransformBlocklist(parsed) // Must happen after typedef transformation
|
2024-08-26 10:49:33 +00:00
|
|
|
|
2024-08-15 07:51:49 +00:00
|
|
|
{
|
|
|
|
// Save the IL file for debug inspection
|
2024-08-14 05:43:54 +00:00
|
|
|
jb, err := json.MarshalIndent(parsed, "", "\t")
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
2024-08-26 10:45:11 +00:00
|
|
|
err = ioutil.WriteFile(cacheFilePath(parsed.Filename)+".ours.json", jb, 0644)
|
2024-08-14 05:43:54 +00:00
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-08-19 07:11:36 +00:00
|
|
|
// Breakout if there is nothing bindable
|
|
|
|
if parsed.Empty() {
|
|
|
|
log.Printf("Nothing in this header was bindable.")
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2024-08-14 05:43:54 +00:00
|
|
|
// Emit 3 code files from the intermediate format
|
2024-08-26 10:45:11 +00:00
|
|
|
outputName := filepath.Join(*outDir, "gen_"+strings.TrimSuffix(filepath.Base(parsed.Filename), `.h`))
|
2024-08-14 05:43:54 +00:00
|
|
|
|
2024-08-26 10:45:11 +00:00
|
|
|
goSrc, err := emitGo(parsed, filepath.Base(parsed.Filename))
|
2024-08-07 06:51:30 +00:00
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
2024-08-10 01:32:43 +00:00
|
|
|
|
2024-08-14 05:43:54 +00:00
|
|
|
err = ioutil.WriteFile(outputName+".go", []byte(goSrc), 0644)
|
2024-08-10 01:32:43 +00:00
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
2024-08-07 06:51:30 +00:00
|
|
|
|
2024-08-26 10:45:11 +00:00
|
|
|
bindingCppSrc, err := emitBindingCpp(parsed, filepath.Base(parsed.Filename))
|
2024-08-14 05:43:54 +00:00
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
2024-08-06 02:29:12 +00:00
|
|
|
|
2024-08-14 05:43:54 +00:00
|
|
|
err = ioutil.WriteFile(outputName+".cpp", []byte(bindingCppSrc), 0644)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
2024-08-06 01:03:23 +00:00
|
|
|
|
2024-08-26 10:45:11 +00:00
|
|
|
bindingHSrc, err := emitBindingHeader(parsed, filepath.Base(parsed.Filename))
|
2024-08-14 05:43:54 +00:00
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
2024-08-06 02:29:12 +00:00
|
|
|
|
2024-08-14 05:43:54 +00:00
|
|
|
err = ioutil.WriteFile(outputName+".h", []byte(bindingHSrc), 0644)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
2024-08-06 02:29:12 +00:00
|
|
|
|
2024-08-14 05:43:54 +00:00
|
|
|
// Done
|
2024-08-06 02:29:12 +00:00
|
|
|
|
|
|
|
}
|
|
|
|
|
2024-08-14 05:43:54 +00:00
|
|
|
log.Printf("Processing %d file(s) completed", len(includeFiles))
|
2024-08-06 01:03:23 +00:00
|
|
|
}
|