github.com/coming-chat/gomobile@v0.0.0-20220601074111-56995f7d7aba/cmd/gomobile/bind_iosapp.go (about) 1 // Copyright 2015 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package main 6 7 import ( 8 "errors" 9 "fmt" 10 "io" 11 "os/exec" 12 "path/filepath" 13 "strconv" 14 "strings" 15 "text/template" 16 17 "golang.org/x/tools/go/packages" 18 ) 19 20 func goAppleBind(gobind string, pkgs []*packages.Package, targets []targetInfo) error { 21 var name string 22 var title string 23 24 if buildO == "" { 25 name = pkgs[0].Name 26 title = strings.Title(name) 27 buildO = title + ".xcframework" 28 } else { 29 if !strings.HasSuffix(buildO, ".xcframework") { 30 return fmt.Errorf("static framework name %q missing .xcframework suffix", buildO) 31 } 32 base := filepath.Base(buildO) 33 name = base[:len(base)-len(".xcframework")] 34 title = strings.Title(name) 35 } 36 37 if err := removeAll(buildO); err != nil { 38 return err 39 } 40 41 modulesUsed, err := areGoModulesUsed() 42 if err != nil { 43 return err 44 } 45 46 var frameworkDirs []string 47 frameworkArchCount := map[string]int{} 48 for _, t := range targets { 49 // Catalyst support requires iOS 13+ 50 v, _ := strconv.ParseFloat(buildIOSVersion, 64) 51 if t.platform == "maccatalyst" && v < 13.0 { 52 return errors.New("catalyst requires -iosversion=13 or higher") 53 } 54 55 outDir := filepath.Join(tmpdir, t.platform) 56 outSrcDir := filepath.Join(outDir, "src") 57 gobindDir := filepath.Join(outSrcDir, "gobind") 58 59 // Run gobind once per platform to generate the bindings 60 cmd := exec.Command( 61 gobind, 62 "-lang=go,objc", 63 "-outdir="+outDir, 64 ) 65 cmd.Env = append(cmd.Env, "GOOS="+platformOS(t.platform)) 66 cmd.Env = append(cmd.Env, "CGO_ENABLED=1") 67 tags := append(buildTags[:], platformTags(t.platform)...) 68 cmd.Args = append(cmd.Args, "-tags="+strings.Join(tags, ",")) 69 if bindPrefix != "" { 70 cmd.Args = append(cmd.Args, "-prefix="+bindPrefix) 71 } 72 for _, p := range pkgs { 73 cmd.Args = append(cmd.Args, p.PkgPath) 74 } 75 if err := runCmd(cmd); err != nil { 76 return err 77 } 78 79 env := appleEnv[t.String()][:] 80 sdk := getenv(env, "DARWIN_SDK") 81 82 frameworkDir := filepath.Join(tmpdir, t.platform, sdk, title+".framework") 83 frameworkDirs = append(frameworkDirs, frameworkDir) 84 frameworkArchCount[frameworkDir] = frameworkArchCount[frameworkDir] + 1 85 86 fileBases := make([]string, len(pkgs)+1) 87 for i, pkg := range pkgs { 88 fileBases[i] = bindPrefix + strings.Title(pkg.Name) 89 } 90 fileBases[len(fileBases)-1] = "Universe" 91 92 // Add the generated packages to GOPATH for reverse bindings. 93 gopath := fmt.Sprintf("GOPATH=%s%c%s", outDir, filepath.ListSeparator, goEnv("GOPATH")) 94 env = append(env, gopath) 95 96 if err := writeGoMod(outDir, t.platform, t.arch); err != nil { 97 return err 98 } 99 100 // Run `go mod tidy` to force to create go.sum. 101 // Without go.sum, `go build` fails as of Go 1.16. 102 if modulesUsed { 103 if err := goModTidyAt(outSrcDir, env); err != nil { 104 return err 105 } 106 } 107 108 path, err := goAppleBindArchive(name+"-"+t.platform+"-"+t.arch, env, outSrcDir) 109 if err != nil { 110 return fmt.Errorf("%s/%s: %v", t.platform, t.arch, err) 111 } 112 113 versionsDir := filepath.Join(frameworkDir, "Versions") 114 versionsADir := filepath.Join(versionsDir, "A") 115 titlePath := filepath.Join(versionsADir, title) 116 if frameworkArchCount[frameworkDir] > 1 { 117 // Not the first static lib, attach to a fat library and skip create headers 118 fatCmd := exec.Command( 119 "xcrun", 120 "lipo", path, titlePath, "-create", "-output", titlePath, 121 ) 122 if err := runCmd(fatCmd); err != nil { 123 return err 124 } 125 continue 126 } 127 128 versionsAHeadersDir := filepath.Join(versionsADir, "Headers") 129 if err := mkdir(versionsAHeadersDir); err != nil { 130 return err 131 } 132 if err := symlink("A", filepath.Join(versionsDir, "Current")); err != nil { 133 return err 134 } 135 if err := symlink("Versions/Current/Headers", filepath.Join(frameworkDir, "Headers")); err != nil { 136 return err 137 } 138 if err := symlink(filepath.Join("Versions/Current", title), filepath.Join(frameworkDir, title)); err != nil { 139 return err 140 } 141 142 lipoCmd := exec.Command( 143 "xcrun", 144 "lipo", path, "-create", "-o", titlePath, 145 ) 146 if err := runCmd(lipoCmd); err != nil { 147 return err 148 } 149 150 // Copy header file next to output archive. 151 var headerFiles []string 152 if len(fileBases) == 1 { 153 headerFiles = append(headerFiles, title+".h") 154 err := copyFile( 155 filepath.Join(versionsAHeadersDir, title+".h"), 156 filepath.Join(gobindDir, bindPrefix+title+".objc.h"), 157 ) 158 if err != nil { 159 return err 160 } 161 } else { 162 for _, fileBase := range fileBases { 163 headerFiles = append(headerFiles, fileBase+".objc.h") 164 err := copyFile( 165 filepath.Join(versionsAHeadersDir, fileBase+".objc.h"), 166 filepath.Join(gobindDir, fileBase+".objc.h"), 167 ) 168 if err != nil { 169 return err 170 } 171 } 172 err := copyFile( 173 filepath.Join(versionsAHeadersDir, "ref.h"), 174 filepath.Join(gobindDir, "ref.h"), 175 ) 176 if err != nil { 177 return err 178 } 179 headerFiles = append(headerFiles, title+".h") 180 err = writeFile(filepath.Join(versionsAHeadersDir, title+".h"), func(w io.Writer) error { 181 return appleBindHeaderTmpl.Execute(w, map[string]interface{}{ 182 "pkgs": pkgs, "title": title, "bases": fileBases, 183 }) 184 }) 185 if err != nil { 186 return err 187 } 188 } 189 190 if err := mkdir(filepath.Join(versionsADir, "Resources")); err != nil { 191 return err 192 } 193 if err := symlink("Versions/Current/Resources", filepath.Join(frameworkDir, "Resources")); err != nil { 194 return err 195 } 196 err = writeFile(filepath.Join(frameworkDir, "Resources", "Info.plist"), func(w io.Writer) error { 197 _, err := w.Write([]byte(appleBindInfoPlist)) 198 return err 199 }) 200 if err != nil { 201 return err 202 } 203 204 var mmVals = struct { 205 Module string 206 Headers []string 207 }{ 208 Module: title, 209 Headers: headerFiles, 210 } 211 err = writeFile(filepath.Join(versionsADir, "Modules", "module.modulemap"), func(w io.Writer) error { 212 return appleModuleMapTmpl.Execute(w, mmVals) 213 }) 214 if err != nil { 215 return err 216 } 217 err = symlink(filepath.Join("Versions/Current/Modules"), filepath.Join(frameworkDir, "Modules")) 218 if err != nil { 219 return err 220 } 221 222 } 223 224 // Finally combine all frameworks to an XCFramework 225 xcframeworkArgs := []string{"-create-xcframework"} 226 227 for _, dir := range frameworkDirs { 228 xcframeworkArgs = append(xcframeworkArgs, "-framework", dir) 229 } 230 231 xcframeworkArgs = append(xcframeworkArgs, "-output", buildO) 232 cmd := exec.Command("xcodebuild", xcframeworkArgs...) 233 err = runCmd(cmd) 234 return err 235 } 236 237 const appleBindInfoPlist = `<?xml version="1.0" encoding="UTF-8"?> 238 <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> 239 <plist version="1.0"> 240 <dict> 241 </dict> 242 </plist> 243 ` 244 245 var appleModuleMapTmpl = template.Must(template.New("iosmmap").Parse(`framework module "{{.Module}}" { 246 header "ref.h" 247 {{range .Headers}} header "{{.}}" 248 {{end}} 249 export * 250 }`)) 251 252 func goAppleBindArchive(name string, env []string, gosrc string) (string, error) { 253 archive := filepath.Join(tmpdir, name+".a") 254 err := goBuildAt(gosrc, "./gobind", env, "-buildmode=c-archive", "-o", archive) 255 if err != nil { 256 return "", err 257 } 258 return archive, nil 259 } 260 261 var appleBindHeaderTmpl = template.Must(template.New("apple.h").Parse(` 262 // Objective-C API for talking to the following Go packages 263 // 264 {{range .pkgs}}// {{.PkgPath}} 265 {{end}}// 266 // File is generated by gomobile bind. Do not edit. 267 #ifndef __{{.title}}_FRAMEWORK_H__ 268 #define __{{.title}}_FRAMEWORK_H__ 269 270 {{range .bases}}#include "{{.}}.objc.h" 271 {{end}} 272 #endif 273 `))