github.com/SahandAslani/gomobile@v0.0.0-20210909130135-2cb2d44c09b2/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  	"fmt"
     9  	"io"
    10  	"os/exec"
    11  	"path/filepath"
    12  	"strings"
    13  	"text/template"
    14  
    15  	"golang.org/x/tools/go/packages"
    16  )
    17  
    18  func goIOSBind(gobind string, pkgs []*packages.Package, archs []string) error {
    19  	// Run gobind to generate the bindings
    20  	cmd := exec.Command(
    21  		gobind,
    22  		"-lang=go,objc",
    23  		"-outdir="+tmpdir,
    24  	)
    25  	cmd.Env = append(cmd.Env, "GOOS=darwin")
    26  	cmd.Env = append(cmd.Env, "CGO_ENABLED=1")
    27  	tags := append(buildTags, "ios")
    28  	cmd.Args = append(cmd.Args, "-tags="+strings.Join(tags, ","))
    29  	if bindPrefix != "" {
    30  		cmd.Args = append(cmd.Args, "-prefix="+bindPrefix)
    31  	}
    32  	for _, p := range pkgs {
    33  		cmd.Args = append(cmd.Args, p.PkgPath)
    34  	}
    35  	if err := runCmd(cmd); err != nil {
    36  		return err
    37  	}
    38  
    39  	srcDir := filepath.Join(tmpdir, "src", "gobind")
    40  
    41  	var name string
    42  	var title string
    43  	if buildO == "" {
    44  		name = pkgs[0].Name
    45  		title = strings.Title(name)
    46  		buildO = title + ".framework"
    47  	} else {
    48  		if !strings.HasSuffix(buildO, ".framework") {
    49  			return fmt.Errorf("static framework name %q missing .framework suffix", buildO)
    50  		}
    51  		base := filepath.Base(buildO)
    52  		name = base[:len(base)-len(".framework")]
    53  		title = strings.Title(name)
    54  	}
    55  
    56  	fileBases := make([]string, len(pkgs)+1)
    57  	for i, pkg := range pkgs {
    58  		fileBases[i] = bindPrefix + strings.Title(pkg.Name)
    59  	}
    60  	fileBases[len(fileBases)-1] = "Universe"
    61  
    62  	cmd = exec.Command("xcrun", "lipo", "-create")
    63  
    64  	modulesUsed, err := areGoModulesUsed()
    65  	if err != nil {
    66  		return err
    67  	}
    68  
    69  	for _, arch := range archs {
    70  		if err := writeGoMod("ios", arch); err != nil {
    71  			return err
    72  		}
    73  
    74  		env := iosEnv[arch]
    75  		// Add the generated packages to GOPATH for reverse bindings.
    76  		gopath := fmt.Sprintf("GOPATH=%s%c%s", tmpdir, filepath.ListSeparator, goEnv("GOPATH"))
    77  		env = append(env, gopath)
    78  
    79  		// Run `go mod tidy` to force to create go.sum.
    80  		// Without go.sum, `go build` fails as of Go 1.16.
    81  		if modulesUsed {
    82  			if err := goModTidyAt(filepath.Join(tmpdir, "src"), env); err != nil {
    83  				return err
    84  			}
    85  		}
    86  
    87  		path, err := goIOSBindArchive(name, env, filepath.Join(tmpdir, "src"))
    88  		if err != nil {
    89  			return fmt.Errorf("ios-%s: %v", arch, err)
    90  		}
    91  		cmd.Args = append(cmd.Args, "-arch", archClang(arch), path)
    92  	}
    93  
    94  	// Build static framework output directory.
    95  	if err := removeAll(buildO); err != nil {
    96  		return err
    97  	}
    98  	headers := buildO + "/Versions/A/Headers"
    99  	if err := mkdir(headers); err != nil {
   100  		return err
   101  	}
   102  	if err := symlink("A", buildO+"/Versions/Current"); err != nil {
   103  		return err
   104  	}
   105  	if err := symlink("Versions/Current/Headers", buildO+"/Headers"); err != nil {
   106  		return err
   107  	}
   108  	if err := symlink("Versions/Current/"+title, buildO+"/"+title); err != nil {
   109  		return err
   110  	}
   111  
   112  	cmd.Args = append(cmd.Args, "-o", buildO+"/Versions/A/"+title)
   113  	if err := runCmd(cmd); err != nil {
   114  		return err
   115  	}
   116  
   117  	// Copy header file next to output archive.
   118  	headerFiles := make([]string, len(fileBases))
   119  	if len(fileBases) == 1 {
   120  		headerFiles[0] = title + ".h"
   121  		err := copyFile(
   122  			headers+"/"+title+".h",
   123  			srcDir+"/"+bindPrefix+title+".objc.h",
   124  		)
   125  		if err != nil {
   126  			return err
   127  		}
   128  	} else {
   129  		for i, fileBase := range fileBases {
   130  			headerFiles[i] = fileBase + ".objc.h"
   131  			err := copyFile(
   132  				headers+"/"+fileBase+".objc.h",
   133  				srcDir+"/"+fileBase+".objc.h")
   134  			if err != nil {
   135  				return err
   136  			}
   137  		}
   138  		err := copyFile(
   139  			headers+"/ref.h",
   140  			srcDir+"/ref.h")
   141  		if err != nil {
   142  			return err
   143  		}
   144  		headerFiles = append(headerFiles, title+".h")
   145  		err = writeFile(headers+"/"+title+".h", func(w io.Writer) error {
   146  			return iosBindHeaderTmpl.Execute(w, map[string]interface{}{
   147  				"pkgs": pkgs, "title": title, "bases": fileBases,
   148  			})
   149  		})
   150  		if err != nil {
   151  			return err
   152  		}
   153  	}
   154  
   155  	resources := buildO + "/Versions/A/Resources"
   156  	if err := mkdir(resources); err != nil {
   157  		return err
   158  	}
   159  	if err := symlink("Versions/Current/Resources", buildO+"/Resources"); err != nil {
   160  		return err
   161  	}
   162  	if err := writeFile(buildO+"/Resources/Info.plist", func(w io.Writer) error {
   163  		_, err := w.Write([]byte(iosBindInfoPlist))
   164  		return err
   165  	}); err != nil {
   166  		return err
   167  	}
   168  
   169  	var mmVals = struct {
   170  		Module  string
   171  		Headers []string
   172  	}{
   173  		Module:  title,
   174  		Headers: headerFiles,
   175  	}
   176  	err = writeFile(buildO+"/Versions/A/Modules/module.modulemap", func(w io.Writer) error {
   177  		return iosModuleMapTmpl.Execute(w, mmVals)
   178  	})
   179  	if err != nil {
   180  		return err
   181  	}
   182  	return symlink("Versions/Current/Modules", buildO+"/Modules")
   183  }
   184  
   185  const iosBindInfoPlist = `<?xml version="1.0" encoding="UTF-8"?>
   186      <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
   187      <plist version="1.0">
   188        <dict>
   189        </dict>
   190      </plist>
   191  `
   192  
   193  var iosModuleMapTmpl = template.Must(template.New("iosmmap").Parse(`framework module "{{.Module}}" {
   194  	header "ref.h"
   195  {{range .Headers}}    header "{{.}}"
   196  {{end}}
   197      export *
   198  }`))
   199  
   200  func goIOSBindArchive(name string, env []string, gosrc string) (string, error) {
   201  	arch := getenv(env, "GOARCH")
   202  	archive := filepath.Join(tmpdir, name+"-"+arch+".a")
   203  	err := goBuildAt(gosrc, "./gobind", env, "-buildmode=c-archive", "-o", archive)
   204  	if err != nil {
   205  		return "", err
   206  	}
   207  	return archive, nil
   208  }
   209  
   210  var iosBindHeaderTmpl = template.Must(template.New("ios.h").Parse(`
   211  // Objective-C API for talking to the following Go packages
   212  //
   213  {{range .pkgs}}//	{{.PkgPath}}
   214  {{end}}//
   215  // File is generated by gomobile bind. Do not edit.
   216  #ifndef __{{.title}}_FRAMEWORK_H__
   217  #define __{{.title}}_FRAMEWORK_H__
   218  
   219  {{range .bases}}#include "{{.}}.objc.h"
   220  {{end}}
   221  #endif
   222  `))