main.go raw

   1  // SPDX-License-Identifier: Unlicense OR MIT
   2  
   3  package main
   4  
   5  import (
   6  	"bytes"
   7  	"errors"
   8  	"flag"
   9  	"fmt"
  10  	"image"
  11  	"image/color"
  12  	"image/png"
  13  	"io"
  14  	"io/ioutil"
  15  	"os"
  16  	"os/exec"
  17  	"path/filepath"
  18  	"strings"
  19  
  20  	"golang.org/x/image/draw"
  21  	"golang.org/x/sync/errgroup"
  22  )
  23  
  24  var (
  25  	target        = flag.String("target", "", "specify target (ios, tvos, android, js).\n")
  26  	archNames     = flag.String("arch", "", "specify architecture(s) to include (arm, arm64, amd64).")
  27  	minsdk        = flag.Int("minsdk", 0, "specify the minimum supported operating system level")
  28  	buildMode     = flag.String("buildmode", "exe", "specify buildmode (archive, exe)")
  29  	destPath      = flag.String("o", "", "output file or directory.\nFor -target ios or tvos, use the .app suffix to target simulators.")
  30  	appID         = flag.String("appid", "", "app identifier (for -buildmode=exe)")
  31  	version       = flag.Int("version", 1, "app version (for -buildmode=exe)")
  32  	printCommands = flag.Bool("x", false, "print the commands")
  33  	keepWorkdir   = flag.Bool("work", false, "print the name of the temporary work directory and do not delete it when exiting.")
  34  	linkMode      = flag.String("linkmode", "", "set the -linkmode flag of the go tool")
  35  	extraLdflags  = flag.String("ldflags", "", "extra flags to the Go linker")
  36  	extraTags     = flag.String("tags", "", "extra tags to the Go tool")
  37  	iconPath      = flag.String("icon", "", "specify an icon for iOS and Android")
  38  	signKey       = flag.String("signkey", "", "specify the path of the keystore to be used to sign Android apk files.")
  39  	signPass      = flag.String("signpass", "", "specify the password to decrypt the signkey.")
  40  	noStrip       = flag.Bool("nostrip", false, "leave debugging symbols in produced .so files")
  41  )
  42  
  43  func main() {
  44  	flag.Usage = func() {
  45  		fmt.Fprint(os.Stderr, mainUsage)
  46  	}
  47  	flag.Parse()
  48  	if err := flagValidate(); err != nil {
  49  		fmt.Fprintf(os.Stderr, "gogio: %v\n", err)
  50  		os.Exit(1)
  51  	}
  52  	buildInfo, err := newBuildInfo(flag.Arg(0))
  53  	if err != nil {
  54  		fmt.Fprintf(os.Stderr, "gogio: %v\n", err)
  55  		os.Exit(1)
  56  	}
  57  	if err := build(buildInfo); err != nil {
  58  		fmt.Fprintf(os.Stderr, "gogio: %v\n", err)
  59  		os.Exit(1)
  60  	}
  61  	os.Exit(0)
  62  }
  63  
  64  func flagValidate() error {
  65  	pkgPathArg := flag.Arg(0)
  66  	if pkgPathArg == "" {
  67  		return errors.New("specify a package")
  68  	}
  69  	if *target == "" {
  70  		return errors.New("please specify -target")
  71  	}
  72  	switch *target {
  73  	case "ios", "tvos", "android", "js", "windows":
  74  	default:
  75  		return fmt.Errorf("invalid -target %s", *target)
  76  	}
  77  	switch *buildMode {
  78  	case "archive", "exe":
  79  	default:
  80  		return fmt.Errorf("invalid -buildmode %s", *buildMode)
  81  	}
  82  	return nil
  83  }
  84  
  85  func build(bi *buildInfo) error {
  86  	tmpDir, err := ioutil.TempDir("", "gogio-")
  87  	if err != nil {
  88  		return err
  89  	}
  90  	if *keepWorkdir {
  91  		fmt.Fprintf(os.Stderr, "WORKDIR=%s\n", tmpDir)
  92  	} else {
  93  		defer os.RemoveAll(tmpDir)
  94  	}
  95  	switch *target {
  96  	case "js":
  97  		return buildJS(bi)
  98  	case "ios", "tvos":
  99  		return buildIOS(tmpDir, *target, bi)
 100  	case "android":
 101  		return buildAndroid(tmpDir, bi)
 102  	case "windows":
 103  		return buildWindows(tmpDir, bi)
 104  	default:
 105  		panic("unreachable")
 106  	}
 107  }
 108  
 109  func runCmdRaw(cmd *exec.Cmd) ([]byte, error) {
 110  	if *printCommands {
 111  		fmt.Printf("%s\n", strings.Join(cmd.Args, " "))
 112  	}
 113  	out, err := cmd.Output()
 114  	if err == nil {
 115  		return out, nil
 116  	}
 117  	if err, ok := err.(*exec.ExitError); ok {
 118  		return nil, fmt.Errorf("%s failed: %s%s", strings.Join(cmd.Args, " "), out, err.Stderr)
 119  	}
 120  	return nil, err
 121  }
 122  
 123  func runCmd(cmd *exec.Cmd) (string, error) {
 124  	out, err := runCmdRaw(cmd)
 125  	return string(bytes.TrimSpace(out)), err
 126  }
 127  
 128  func copyFile(dst, src string) (err error) {
 129  	r, err := os.Open(src)
 130  	if err != nil {
 131  		return err
 132  	}
 133  	defer r.Close()
 134  	w, err := os.Create(dst)
 135  	if err != nil {
 136  		return err
 137  	}
 138  	defer func() {
 139  		if cerr := w.Close(); err == nil {
 140  			err = cerr
 141  		}
 142  	}()
 143  	_, err = io.Copy(w, r)
 144  	return err
 145  }
 146  
 147  type arch struct {
 148  	iosArch   string
 149  	jniArch   string
 150  	clangArch string
 151  }
 152  
 153  var allArchs = map[string]arch{
 154  	"arm": {
 155  		iosArch:   "armv7",
 156  		jniArch:   "armeabi-v7a",
 157  		clangArch: "armv7a-linux-androideabi",
 158  	},
 159  	"arm64": {
 160  		iosArch:   "arm64",
 161  		jniArch:   "arm64-v8a",
 162  		clangArch: "aarch64-linux-android",
 163  	},
 164  	"386": {
 165  		iosArch:   "i386",
 166  		jniArch:   "x86",
 167  		clangArch: "i686-linux-android",
 168  	},
 169  	"amd64": {
 170  		iosArch:   "x86_64",
 171  		jniArch:   "x86_64",
 172  		clangArch: "x86_64-linux-android",
 173  	},
 174  }
 175  
 176  type iconVariant struct {
 177  	path string
 178  	size int
 179  	fill bool
 180  }
 181  
 182  func buildIcons(baseDir, icon string, variants []iconVariant) error {
 183  	f, err := os.Open(icon)
 184  	if err != nil {
 185  		return err
 186  	}
 187  	defer f.Close()
 188  	img, _, err := image.Decode(f)
 189  	if err != nil {
 190  		return err
 191  	}
 192  	var resizes errgroup.Group
 193  	for _, v := range variants {
 194  		v := v
 195  		resizes.Go(func() (err error) {
 196  			path := filepath.Join(baseDir, v.path)
 197  			if err := os.MkdirAll(filepath.Dir(path), 0700); err != nil {
 198  				return err
 199  			}
 200  			f, err := os.Create(path)
 201  			if err != nil {
 202  				return err
 203  			}
 204  			defer func() {
 205  				if cerr := f.Close(); err == nil {
 206  					err = cerr
 207  				}
 208  			}()
 209  			return png.Encode(f, resizeIcon(v, img))
 210  		})
 211  	}
 212  	return resizes.Wait()
 213  }
 214  
 215  func resizeIcon(v iconVariant, img image.Image) *image.NRGBA {
 216  	scaled := image.NewNRGBA(image.Rectangle{Max: image.Point{X: v.size, Y: v.size}})
 217  	op := draw.Src
 218  	if v.fill {
 219  		op = draw.Over
 220  		draw.Draw(scaled, scaled.Bounds(), &image.Uniform{color.White}, image.Point{}, draw.Src)
 221  	}
 222  	draw.CatmullRom.Scale(scaled, scaled.Bounds(), img, img.Bounds(), op, nil)
 223  
 224  	return scaled
 225  }
 226