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  `))