github.com/cybriq/giocore@v0.0.7-0.20210703034601-cfb9cb5f3900/cmd/gogio/main.go (about)

     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  )
    41  
    42  func main() {
    43  	flag.Usage = func() {
    44  		fmt.Fprint(os.Stderr, mainUsage)
    45  	}
    46  	flag.Parse()
    47  	if err := flagValidate(); err != nil {
    48  		fmt.Fprintf(os.Stderr, "gogio: %v\n", err)
    49  		os.Exit(1)
    50  	}
    51  	buildInfo, err := newBuildInfo(flag.Arg(0))
    52  	if err != nil {
    53  		fmt.Fprintf(os.Stderr, "gogio: %v\n", err)
    54  		os.Exit(1)
    55  	}
    56  	if err := build(buildInfo); err != nil {
    57  		fmt.Fprintf(os.Stderr, "gogio: %v\n", err)
    58  		os.Exit(1)
    59  	}
    60  	os.Exit(0)
    61  }
    62  
    63  func flagValidate() error {
    64  	pkgPathArg := flag.Arg(0)
    65  	if pkgPathArg == "" {
    66  		return errors.New("specify a package")
    67  	}
    68  	if *target == "" {
    69  		return errors.New("please specify -target")
    70  	}
    71  	switch *target {
    72  	case "ios", "tvos", "android", "js", "windows":
    73  	default:
    74  		return fmt.Errorf("invalid -target %s", *target)
    75  	}
    76  	switch *buildMode {
    77  	case "archive", "exe":
    78  	default:
    79  		return fmt.Errorf("invalid -buildmode %s", *buildMode)
    80  	}
    81  	return nil
    82  }
    83  
    84  func build(bi *buildInfo) error {
    85  	tmpDir, err := ioutil.TempDir("", "gogio-")
    86  	if err != nil {
    87  		return err
    88  	}
    89  	if *keepWorkdir {
    90  		fmt.Fprintf(os.Stderr, "WORKDIR=%s\n", tmpDir)
    91  	} else {
    92  		defer os.RemoveAll(tmpDir)
    93  	}
    94  	switch *target {
    95  	case "js":
    96  		return buildJS(bi)
    97  	case "ios", "tvos":
    98  		return buildIOS(tmpDir, *target, bi)
    99  	case "android":
   100  		return buildAndroid(tmpDir, bi)
   101  	case "windows":
   102  		return buildWindows(tmpDir, bi)
   103  	default:
   104  		panic("unreachable")
   105  	}
   106  }
   107  
   108  func runCmdRaw(cmd *exec.Cmd) ([]byte, error) {
   109  	if *printCommands {
   110  		fmt.Printf("%s\n", strings.Join(cmd.Args, " "))
   111  	}
   112  	out, err := cmd.Output()
   113  	if err == nil {
   114  		return out, nil
   115  	}
   116  	if err, ok := err.(*exec.ExitError); ok {
   117  		return nil, fmt.Errorf("%s failed: %s%s", strings.Join(cmd.Args, " "), out, err.Stderr)
   118  	}
   119  	return nil, err
   120  }
   121  
   122  func runCmd(cmd *exec.Cmd) (string, error) {
   123  	out, err := runCmdRaw(cmd)
   124  	return string(bytes.TrimSpace(out)), err
   125  }
   126  
   127  func copyFile(dst, src string) (err error) {
   128  	r, err := os.Open(src)
   129  	if err != nil {
   130  		return err
   131  	}
   132  	defer r.Close()
   133  	w, err := os.Create(dst)
   134  	if err != nil {
   135  		return err
   136  	}
   137  	defer func() {
   138  		if cerr := w.Close(); err == nil {
   139  			err = cerr
   140  		}
   141  	}()
   142  	_, err = io.Copy(w, r)
   143  	return err
   144  }
   145  
   146  type arch struct {
   147  	iosArch   string
   148  	jniArch   string
   149  	clangArch string
   150  }
   151  
   152  var allArchs = map[string]arch{
   153  	"arm": {
   154  		iosArch:   "armv7",
   155  		jniArch:   "armeabi-v7a",
   156  		clangArch: "armv7a-linux-androideabi",
   157  	},
   158  	"arm64": {
   159  		iosArch:   "arm64",
   160  		jniArch:   "arm64-v8a",
   161  		clangArch: "aarch64-linux-android",
   162  	},
   163  	"386": {
   164  		iosArch:   "i386",
   165  		jniArch:   "x86",
   166  		clangArch: "i686-linux-android",
   167  	},
   168  	"amd64": {
   169  		iosArch:   "x86_64",
   170  		jniArch:   "x86_64",
   171  		clangArch: "x86_64-linux-android",
   172  	},
   173  }
   174  
   175  type iconVariant struct {
   176  	path string
   177  	size int
   178  	fill bool
   179  }
   180  
   181  func buildIcons(baseDir, icon string, variants []iconVariant) error {
   182  	f, err := os.Open(icon)
   183  	if err != nil {
   184  		return err
   185  	}
   186  	defer f.Close()
   187  	img, _, err := image.Decode(f)
   188  	if err != nil {
   189  		return err
   190  	}
   191  	var resizes errgroup.Group
   192  	for _, v := range variants {
   193  		v := v
   194  		resizes.Go(func() (err error) {
   195  			path := filepath.Join(baseDir, v.path)
   196  			if err := os.MkdirAll(filepath.Dir(path), 0700); err != nil {
   197  				return err
   198  			}
   199  			f, err := os.Create(path)
   200  			if err != nil {
   201  				return err
   202  			}
   203  			defer func() {
   204  				if cerr := f.Close(); err == nil {
   205  					err = cerr
   206  				}
   207  			}()
   208  			return png.Encode(f, resizeIcon(v, img))
   209  		})
   210  	}
   211  	return resizes.Wait()
   212  }
   213  
   214  func resizeIcon(v iconVariant, img image.Image) *image.NRGBA {
   215  	scaled := image.NewNRGBA(image.Rectangle{Max: image.Point{X: v.size, Y: v.size}})
   216  	op := draw.Src
   217  	if v.fill {
   218  		op = draw.Over
   219  		draw.Draw(scaled, scaled.Bounds(), &image.Uniform{color.White}, image.Point{}, draw.Src)
   220  	}
   221  	draw.CatmullRom.Scale(scaled, scaled.Bounds(), img, img.Bounds(), op, nil)
   222  
   223  	return scaled
   224  }