github.com/SpiderOak/mobile@v0.0.0-20221129182558-6f541b59af45/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/sync/errgroup" 18 "golang.org/x/tools/go/packages" 19 ) 20 21 func goAppleBind(pkgs []*packages.Package, targets []targetInfo) error { 22 var name string 23 var title string 24 25 if buildO == "" { 26 name = pkgs[0].Name 27 title = strings.Title(name) 28 buildO = title + ".xcframework" 29 } else { 30 if !strings.HasSuffix(buildO, ".xcframework") { 31 return fmt.Errorf("static framework name %q missing .xcframework suffix", buildO) 32 } 33 base := filepath.Base(buildO) 34 name = base[:len(base)-len(".xcframework")] 35 title = strings.Title(name) 36 } 37 38 if err := removeAll(buildO); err != nil { 39 return err 40 } 41 42 outDirsForPlatform := map[string]string{} 43 for _, t := range targets { 44 outDirsForPlatform[t.platform] = filepath.Join(tmpdir, t.platform) 45 } 46 47 // Run the gobind command for each platform 48 var gobindWG errgroup.Group 49 for platform, outDir := range outDirsForPlatform { 50 platform := platform 51 outDir := outDir 52 gobindWG.Go(func() error { 53 // Catalyst support requires iOS 13+ 54 v, _ := strconv.ParseFloat(buildIOSVersion, 64) 55 if platform == "maccatalyst" && v < 13.0 { 56 return errors.New("catalyst requires -iosversion=13 or higher") 57 } 58 59 // Run gobind once per platform to generate the bindings 60 cmd := exec.Command( 61 "go", "run", "github.com/SpiderOak/mobile/cmd/gobind", 62 "-lang=go,objc", 63 "-outdir="+outDir, 64 ) 65 cmd.Env = append(cmd.Env, "GOOS="+platformOS(platform)) 66 cmd.Env = append(cmd.Env, "CGO_ENABLED=1") 67 tags := append(buildTags[:], platformTags(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 return nil 79 }) 80 } 81 if err := gobindWG.Wait(); err != nil { 82 return err 83 } 84 85 modPath, err := goModPath() 86 if err != nil { 87 return err 88 } 89 modulesUsed := modPath != "" 90 91 // Build archive files. 92 var buildWG errgroup.Group 93 for _, t := range targets { 94 t := t 95 buildWG.Go(func() error { 96 outDir := outDirsForPlatform[t.platform] 97 outSrcDir := filepath.Join(outDir, "src") 98 99 if modulesUsed { 100 // Copy the source directory for each architecture for concurrent building. 101 newOutSrcDir := filepath.Join(outDir, "src-"+t.arch) 102 if !buildN { 103 if err := doCopyAll(newOutSrcDir, outSrcDir); err != nil { 104 return err 105 } 106 } 107 outSrcDir = newOutSrcDir 108 } 109 110 // Copy the environment variables to make this function concurrent-safe. 111 env := make([]string, len(appleEnv[t.String()])) 112 copy(env, appleEnv[t.String()]) 113 114 // Add the generated packages to GOPATH for reverse bindings. 115 gopath := fmt.Sprintf("GOPATH=%s%c%s", outDir, filepath.ListSeparator, goEnv("GOPATH")) 116 env = append(env, gopath) 117 118 // Run `go mod tidy` to force to create go.sum. 119 // Without go.sum, `go build` fails as of Go 1.16. 120 if modulesUsed { 121 if err := writeGoMod(modPath, outSrcDir); err != nil { 122 return err 123 } 124 if err := goModTidyAt(outSrcDir, env); err != nil { 125 return err 126 } 127 } 128 129 if err := goAppleBindArchive(appleArchiveFilepath(name, t), env, outSrcDir); err != nil { 130 return fmt.Errorf("%s/%s: %v", t.platform, t.arch, err) 131 } 132 133 return nil 134 }) 135 } 136 if err := buildWG.Wait(); err != nil { 137 return err 138 } 139 140 var frameworkDirs []string 141 frameworkArchCount := map[string]int{} 142 for _, t := range targets { 143 outDir := outDirsForPlatform[t.platform] 144 gobindDir := filepath.Join(outDir, "src", "gobind") 145 146 env := appleEnv[t.String()][:] 147 sdk := getenv(env, "DARWIN_SDK") 148 149 frameworkDir := filepath.Join(tmpdir, t.platform, sdk, title+".framework") 150 frameworkDirs = append(frameworkDirs, frameworkDir) 151 frameworkArchCount[frameworkDir] = frameworkArchCount[frameworkDir] + 1 152 153 versionsDir := filepath.Join(frameworkDir, "Versions") 154 versionsADir := filepath.Join(versionsDir, "A") 155 titlePath := filepath.Join(versionsADir, title) 156 if frameworkArchCount[frameworkDir] > 1 { 157 // Not the first static lib, attach to a fat library and skip create headers 158 fatCmd := exec.Command( 159 "xcrun", 160 "lipo", appleArchiveFilepath(name, t), titlePath, "-create", "-output", titlePath, 161 ) 162 if err := runCmd(fatCmd); err != nil { 163 return err 164 } 165 continue 166 } 167 168 versionsAHeadersDir := filepath.Join(versionsADir, "Headers") 169 if err := mkdir(versionsAHeadersDir); err != nil { 170 return err 171 } 172 if err := symlink("A", filepath.Join(versionsDir, "Current")); err != nil { 173 return err 174 } 175 if err := symlink("Versions/Current/Headers", filepath.Join(frameworkDir, "Headers")); err != nil { 176 return err 177 } 178 if err := symlink(filepath.Join("Versions/Current", title), filepath.Join(frameworkDir, title)); err != nil { 179 return err 180 } 181 182 lipoCmd := exec.Command( 183 "xcrun", 184 "lipo", appleArchiveFilepath(name, t), "-create", "-o", titlePath, 185 ) 186 if err := runCmd(lipoCmd); err != nil { 187 return err 188 } 189 190 fileBases := make([]string, len(pkgs)+1) 191 for i, pkg := range pkgs { 192 fileBases[i] = bindPrefix + strings.Title(pkg.Name) 193 } 194 fileBases[len(fileBases)-1] = "Universe" 195 196 // Copy header file next to output archive. 197 var headerFiles []string 198 if len(fileBases) == 1 { 199 headerFiles = append(headerFiles, title+".h") 200 err := copyFile( 201 filepath.Join(versionsAHeadersDir, title+".h"), 202 filepath.Join(gobindDir, bindPrefix+title+".objc.h"), 203 ) 204 if err != nil { 205 return err 206 } 207 } else { 208 for _, fileBase := range fileBases { 209 headerFiles = append(headerFiles, fileBase+".objc.h") 210 err := copyFile( 211 filepath.Join(versionsAHeadersDir, fileBase+".objc.h"), 212 filepath.Join(gobindDir, fileBase+".objc.h"), 213 ) 214 if err != nil { 215 return err 216 } 217 } 218 err := copyFile( 219 filepath.Join(versionsAHeadersDir, "ref.h"), 220 filepath.Join(gobindDir, "ref.h"), 221 ) 222 if err != nil { 223 return err 224 } 225 headerFiles = append(headerFiles, title+".h") 226 err = writeFile(filepath.Join(versionsAHeadersDir, title+".h"), func(w io.Writer) error { 227 return appleBindHeaderTmpl.Execute(w, map[string]interface{}{ 228 "pkgs": pkgs, "title": title, "bases": fileBases, 229 }) 230 }) 231 if err != nil { 232 return err 233 } 234 } 235 236 if err := mkdir(filepath.Join(versionsADir, "Resources")); err != nil { 237 return err 238 } 239 if err := symlink("Versions/Current/Resources", filepath.Join(frameworkDir, "Resources")); err != nil { 240 return err 241 } 242 err = writeFile(filepath.Join(frameworkDir, "Resources", "Info.plist"), func(w io.Writer) error { 243 _, err := w.Write([]byte(appleBindInfoPlist)) 244 return err 245 }) 246 if err != nil { 247 return err 248 } 249 250 var mmVals = struct { 251 Module string 252 Headers []string 253 }{ 254 Module: title, 255 Headers: headerFiles, 256 } 257 err = writeFile(filepath.Join(versionsADir, "Modules", "module.modulemap"), func(w io.Writer) error { 258 return appleModuleMapTmpl.Execute(w, mmVals) 259 }) 260 if err != nil { 261 return err 262 } 263 err = symlink(filepath.Join("Versions/Current/Modules"), filepath.Join(frameworkDir, "Modules")) 264 if err != nil { 265 return err 266 } 267 } 268 269 // Finally combine all frameworks to an XCFramework 270 xcframeworkArgs := []string{"-create-xcframework"} 271 272 for _, dir := range frameworkDirs { 273 xcframeworkArgs = append(xcframeworkArgs, "-framework", dir) 274 } 275 276 xcframeworkArgs = append(xcframeworkArgs, "-output", buildO) 277 cmd := exec.Command("xcodebuild", xcframeworkArgs...) 278 err = runCmd(cmd) 279 return err 280 } 281 282 const appleBindInfoPlist = `<?xml version="1.0" encoding="UTF-8"?> 283 <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> 284 <plist version="1.0"> 285 <dict> 286 </dict> 287 </plist> 288 ` 289 290 var appleModuleMapTmpl = template.Must(template.New("iosmmap").Parse(`framework module "{{.Module}}" { 291 header "ref.h" 292 {{range .Headers}} header "{{.}}" 293 {{end}} 294 export * 295 }`)) 296 297 func appleArchiveFilepath(name string, t targetInfo) string { 298 return filepath.Join(tmpdir, name+"-"+t.platform+"-"+t.arch+".a") 299 } 300 301 func goAppleBindArchive(out string, env []string, gosrc string) error { 302 return goBuildAt(gosrc, "./gobind", env, "-buildmode=c-archive", "-o", out) 303 } 304 305 var appleBindHeaderTmpl = template.Must(template.New("apple.h").Parse(` 306 // Objective-C API for talking to the following Go packages 307 // 308 {{range .pkgs}}// {{.PkgPath}} 309 {{end}}// 310 // File is generated by gomobile bind. Do not edit. 311 #ifndef __{{.title}}_FRAMEWORK_H__ 312 #define __{{.title}}_FRAMEWORK_H__ 313 314 {{range .bases}}#include "{{.}}.objc.h" 315 {{end}} 316 #endif 317 `))