github.com/provpn/mobile@v0.0.0-20210315122651-28c475f89f6c/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 arch == "x86" {
    71  			arch = "amd64"
    72  		}
    73  
    74  		if err := writeGoMod("darwin", arch); err != nil {
    75  			return err
    76  		}
    77  
    78  		env := darwinEnv[arch]
    79  		// Add the generated packages to GOPATH for reverse bindings.
    80  		gopath := fmt.Sprintf("GOPATH=%s%c%s", tmpdir, filepath.ListSeparator, goEnv("GOPATH"))
    81  		env = append(env, gopath)
    82  
    83  		// Run `go mod tidy` to force to create go.sum.
    84  		// Without go.sum, `go build` fails as of Go 1.16.
    85  		if modulesUsed {
    86  			if err := goModTidyAt(filepath.Join(tmpdir, "src"), env); err != nil {
    87  				return err
    88  			}
    89  		}
    90  
    91  		path, err := goIOSBindArchive(name, env, filepath.Join(tmpdir, "src"))
    92  		if err != nil {
    93  			return fmt.Errorf("darwin-%s: %v", arch, err)
    94  		}
    95  		cmd.Args = append(cmd.Args, "-arch", archClang(arch), path)
    96  	}
    97  
    98  	// Build static framework output directory.
    99  	if err := removeAll(buildO); err != nil {
   100  		return err
   101  	}
   102  	headers := buildO + "/Versions/A/Headers"
   103  	if err := mkdir(headers); err != nil {
   104  		return err
   105  	}
   106  	if err := symlink("A", buildO+"/Versions/Current"); err != nil {
   107  		return err
   108  	}
   109  	if err := symlink("Versions/Current/Headers", buildO+"/Headers"); err != nil {
   110  		return err
   111  	}
   112  	if err := symlink("Versions/Current/"+title, buildO+"/"+title); err != nil {
   113  		return err
   114  	}
   115  
   116  	cmd.Args = append(cmd.Args, "-o", buildO+"/Versions/A/"+title)
   117  	if err := runCmd(cmd); err != nil {
   118  		return err
   119  	}
   120  
   121  	// Copy header file next to output archive.
   122  	headerFiles := make([]string, len(fileBases))
   123  	if len(fileBases) == 1 {
   124  		headerFiles[0] = title + ".h"
   125  		err := copyFile(
   126  			headers+"/"+title+".h",
   127  			srcDir+"/"+bindPrefix+title+".objc.h",
   128  		)
   129  		if err != nil {
   130  			return err
   131  		}
   132  	} else {
   133  		for i, fileBase := range fileBases {
   134  			headerFiles[i] = fileBase + ".objc.h"
   135  			err := copyFile(
   136  				headers+"/"+fileBase+".objc.h",
   137  				srcDir+"/"+fileBase+".objc.h")
   138  			if err != nil {
   139  				return err
   140  			}
   141  		}
   142  		err := copyFile(
   143  			headers+"/ref.h",
   144  			srcDir+"/ref.h")
   145  		if err != nil {
   146  			return err
   147  		}
   148  		headerFiles = append(headerFiles, title+".h")
   149  		err = writeFile(headers+"/"+title+".h", func(w io.Writer) error {
   150  			return iosBindHeaderTmpl.Execute(w, map[string]interface{}{
   151  				"pkgs": pkgs, "title": title, "bases": fileBases,
   152  			})
   153  		})
   154  		if err != nil {
   155  			return err
   156  		}
   157  	}
   158  
   159  	resources := buildO + "/Versions/A/Resources"
   160  	if err := mkdir(resources); err != nil {
   161  		return err
   162  	}
   163  	if err := symlink("Versions/Current/Resources", buildO+"/Resources"); err != nil {
   164  		return err
   165  	}
   166  	if err := writeFile(buildO+"/Resources/Info.plist", func(w io.Writer) error {
   167  		_, err := w.Write([]byte(iosBindInfoPlist))
   168  		return err
   169  	}); err != nil {
   170  		return err
   171  	}
   172  
   173  	var mmVals = struct {
   174  		Module  string
   175  		Headers []string
   176  	}{
   177  		Module:  title,
   178  		Headers: headerFiles,
   179  	}
   180  	err = writeFile(buildO+"/Versions/A/Modules/module.modulemap", func(w io.Writer) error {
   181  		return iosModuleMapTmpl.Execute(w, mmVals)
   182  	})
   183  	if err != nil {
   184  		return err
   185  	}
   186  	return symlink("Versions/Current/Modules", buildO+"/Modules")
   187  }
   188  
   189  const iosBindInfoPlist = `<?xml version="1.0" encoding="UTF-8"?>
   190      <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
   191      <plist version="1.0">
   192        <dict>
   193        </dict>
   194      </plist>
   195  `
   196  
   197  var iosModuleMapTmpl = template.Must(template.New("iosmmap").Parse(`framework module "{{.Module}}" {
   198  	header "ref.h"
   199  {{range .Headers}}    header "{{.}}"
   200  {{end}}
   201      export *
   202  }`))
   203  
   204  func goIOSBindArchive(name string, env []string, gosrc string) (string, error) {
   205  	arch := getenv(env, "GOARCH")
   206  	archive := filepath.Join(tmpdir, name+"-"+arch+".a")
   207  	err := goBuildAt(gosrc, "./gobind", env, "-buildmode=c-archive", "-o", archive)
   208  	if err != nil {
   209  		return "", err
   210  	}
   211  	return archive, nil
   212  }
   213  
   214  var iosBindHeaderTmpl = template.Must(template.New("ios.h").Parse(`
   215  // Objective-C API for talking to the following Go packages
   216  //
   217  {{range .pkgs}}//	{{.PkgPath}}
   218  {{end}}//
   219  // File is generated by gomobile bind. Do not edit.
   220  #ifndef __{{.title}}_FRAMEWORK_H__
   221  #define __{{.title}}_FRAMEWORK_H__
   222  
   223  {{range .bases}}#include "{{.}}.objc.h"
   224  {{end}}
   225  #endif
   226  `))