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 }