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