Skip to content

Commit

Permalink
generate: add structs tool
Browse files Browse the repository at this point in the history
  • Loading branch information
progrium committed Nov 14, 2023
1 parent 5cbb735 commit 91bc7ac
Show file tree
Hide file tree
Showing 6 changed files with 157 additions and 65 deletions.
13 changes: 10 additions & 3 deletions docs/generation.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,13 @@ Once it generates good output, you can pipe it to the appropriate file under ./g

The [initmod](#initmod-platform-framework) tool can now be run to generate the initial files for the package that are not intended to be generated again. At this point you won't have to modify anything in these yet, they're good as is.

This is also where you should generate structs. This is done out of band from regular generation
so that they can be tweaked manually if needed. You can use the [structs](#structs) tool like this:

`go run ./generate/tools/structs.go [framework] > ./macos/[framework]/[framework]_structs.go`

Be sure to check the output! If it was unable to generate a struct it will include it in the comments at the bottom. If any other struct has a field using a struct type it couldn't generate it will start with ` _Ctype_struct_`, which you'll need to comment out or replace with a placeholder until you have that type.


### Step 4: Run go generate for your framework

Expand Down Expand Up @@ -220,8 +227,8 @@ Re-generates frameworks that have been generated (have .gen.go files).

`./generate/tools/regen.sh macos`

### findstruct [struct-name]
### structs [framework]

Looks through XCode SDK headers for a struct definition. Helpful to fill in struct definitions.
Generates documented Go structs for a framework.

`go run ./generate/tools/findstruct.go CLLocationCoordinate2D`
`go run ./generate/tools/structs.go foundation > ./macos/foundation/foundation_structs.go`
3 changes: 3 additions & 0 deletions generate/generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,9 @@ func (db *Generator) Generate(platform string, version int, rootDir string, fram
})
continue
}
// any other type aliases can be added manually
// since they're just a go type alias or an
// unsafe.Pointer type
}
}
mw.WriteCode()
Expand Down
2 changes: 1 addition & 1 deletion generate/modules/modules.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ var All = []Module{
{"AVFoundation", "AVFoundation", "avfoundation", "AVFoundation/AVFoundation.h", []string{"AV"}},
{"AVKit", "AVKit", "avkit", "AVKit/AVKit.h", []string{"AV"}},
{"GameplayKit", "GameplayKit", "gameplaykit", "GameplayKit/GameplayKit.h", []string{"GK"}},
{"SystemConfiguration", "System Configuration", "sysconfig", "SystemConfiguration/SystemConfiguration.h", []string{"SC", "kSC"}},
{"SceneKit", "SceneKit", "scenekit", "SceneKit/SceneKit.h", []string{"SCN"}},
{"SpriteKit", "SpriteKit", "spritekit", "SpriteKit/SpriteKit.h", []string{"SK"}},
{"ModelIO", "Model I/O", "modelio", "ModelIO/ModelIO.h", []string{"MDL"}},
Expand All @@ -156,6 +157,5 @@ var All = []Module{
{"MetalKit", "Metal Kit", "metalkit", "MetalKit/MetalKit.h", []string{"MTK"}},
{"MetalPerformanceShadersGraph", "Metal Performance Shaders Graph", "mpsgraph", "MetalPerformanceShadersGraph/MetalPerformanceShadersGraph.h", []string{"MPSGraph"}},
{"MetalPerformanceShaders", "Metal Performance Shaders", "mps", "MetalPerformanceShaders/MetalPerformanceShaders.h", []string{"MPS"}},
{"SystemConfiguration", "System Configuration", "sysconfig", "SystemConfiguration/SystemConfiguration.h", []string{"SC", "kSC"}},
{"MediaPlayer", "Media Player", "mediaplayer", "MediaPlayer/MediaPlayer.h", []string{"MP"}},
}
2 changes: 1 addition & 1 deletion generate/symbols.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ var pathBlacklist = []string{
type Symbol struct {
Name string
Path string
Kind string
Kind string // Class, Constant, Enum, Framework, Function, Macro, Method, Property, Protocol, Struct, Type

Description string
Type string
Expand Down
60 changes: 0 additions & 60 deletions generate/tools/findstruct.go

This file was deleted.

142 changes: 142 additions & 0 deletions generate/tools/structs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
//go:build ignore

package main

import (
"fmt"
"io/ioutil"
"log"
"os"
"os/exec"
"strings"

"github.com/progrium/macdriver/generate"
"github.com/progrium/macdriver/generate/modules"
)

const TargetPlatform = "macos"
const TargetVersion = 12

// go run ./generate/tools/structs.go [framework]
func main() {

db, err := generate.OpenSymbols("./generate/symbols.zip")
if err != nil {
log.Fatal(err)
}
defer db.Close()

if len(os.Args) > 1 {
mod := modules.Get(strings.ToLower(os.Args[1]))

var structDefs []string
var badStructs []string
docComments := make(map[string]string)

for _, s := range db.ModuleSymbols(mod.Package) {
if TargetPlatform == "macos" && mod.Package == "uikit" {
// we're going to pretend to be appkit later
// to handle the uikit symbols existing in appkit
if !s.HasFramework("appkit") {
continue
}
}
if !s.HasPlatform(TargetPlatform, TargetVersion, true) {
continue
}

if s.Kind == "Struct" {

structDef := fmt.Sprintf("type %s C.%s\n", modules.TrimPrefix(s.Name), s.Name)

// generate for just this struct to see that it works
// before adding so the full list generates without error.
// however we can't just use this because godefs will only
// use the right sub struct type if they're generated together.
_, err := goDefsFor(mod, []string{structDef})
if err != nil {
badStructs = append(badStructs, s.Name)
continue
}

if s.DocURL() != "" {
// godefs strips comments so we'll have to reassemble with
// comments using this
docComments[modules.TrimPrefix(s.Name)] = fmt.Sprintf("// %s [Full Topic]\n//\n// [Full Topic]: %s\n", s.Description, s.DocURL())
}

structDefs = append(structDefs, structDef)
}
}

if len(structDefs) == 0 {
fmt.Fprintln(os.Stderr, "No structs for this framework")
os.Exit(1)
}

out, err := goDefsFor(mod, structDefs)
if err != nil {
fmt.Println(out)
log.Fatal(err)
}

// *byte and *[0]byte fields should just be uintptr
out = strings.ReplaceAll(out, "*byte", "uintptr")
out = strings.ReplaceAll(out, "*[0]byte", "uintptr")

// reassemble with comments
fmt.Println("package", mod.Package, "\n")
parts := strings.Split(out, "type ")
for _, p := range parts[1:] {
words := strings.Fields(p)
if s, ok := docComments[words[0]]; ok {
fmt.Print(s)
}
fmt.Println("type", p)
}

if len(badStructs) > 0 {
fmt.Println("// TODO (unable to generate):")
fmt.Println("//", strings.Join(badStructs, " "))
}
}

}

func goDefsFor(mod *modules.Module, structDefs []string) (string, error) {
extraLoad := ""
extraInclude := ""
source := fmt.Sprintf(`package main
/*
#cgo CFLAGS: -w -x objective-c
#cgo LDFLAGS: -lobjc -framework %s %s
#include <%s>
%s
*/
import "C"
%s
`, mod.Name, extraLoad, mod.Header, extraInclude, strings.Join(structDefs, "\n"))
b, err := evalSource(source)
return string(b), err
}

func evalSource(source string) ([]byte, error) {
tempFile, err := ioutil.TempFile("", "temp-code-*.go")
if err != nil {
log.Fatal("Failed to create temporary file:", err)
}
defer os.Remove(tempFile.Name())

if _, err := tempFile.WriteString(source); err != nil {
log.Fatal("Failed to write to temporary file:", err)
}

if err := tempFile.Close(); err != nil {
log.Fatal("Failed to close temporary file:", err)
}

cmd := exec.Command("go", "tool", "cgo", "-godefs", "--", "-x", "objective-c", tempFile.Name())
return cmd.CombinedOutput()
}

0 comments on commit 91bc7ac

Please sign in to comment.