commit 3f8353843d8ef9e42b5f8d743b62c232e1b06c7e
parent 5cd278b05e0d9343015a503fa5af28cbafdcd843
Author: Jack Mordaunt <jackmordaunt@gmail.com>
Date: Mon, 9 Nov 2020 16:57:53 +0800
chore: release macOS app bundle
Diffstat:
5 files changed, 191 insertions(+), 0 deletions(-)
diff --git a/cmd/release/main.go b/cmd/release/main.go
@@ -0,0 +1,146 @@
+// release is a tool for distributing OS specific packages to Windows, macOS
+// and Linux.
+package main
+
+import (
+ "fmt"
+ "image/png"
+ "io"
+ "os"
+ "os/exec"
+ "path/filepath"
+
+ "github.com/jackmordaunt/icns"
+)
+
+// TODO:
+// - Release for windows/linux
+// - configure things?
+// - zip results
+// - push to github releases
+// - tag a release -> upload to github
+func main() {
+ if err := func() error {
+ binary, err := build("cmd/kanban")
+ if err != nil {
+ return fmt.Errorf("building: %w", err)
+ }
+ if err := bundle("dist/Kanban.app", binary, "res/icon.png", "res/darwin/Info.plist"); err != nil {
+ return fmt.Errorf("bundling: %w", err)
+ }
+ return nil
+ }(); err != nil {
+ fmt.Printf("error: %v", err)
+ }
+}
+
+// bundle creates a macOS .app bundle on disk rooted at dest.
+// All paramaters are filepaths.
+// NB: Will clobber destination if it is a directory, or error if it is a file.
+func bundle(dest, binary, icon, plist string) error {
+ var (
+ contents = filepath.Join(dest, "Contents")
+ macos = filepath.Join(contents, "MacOS")
+ resources = filepath.Join(contents, "Resources")
+ )
+ m, err := os.Stat(dest)
+ if os.IsNotExist(err) || m.IsDir() {
+ os.RemoveAll(dest)
+ if err := os.MkdirAll(dest, 0777); err != nil {
+ return fmt.Errorf("preparing destination: %w", err)
+ }
+ } else if !m.IsDir() {
+ return fmt.Errorf("destination %q: not a directory", dest)
+ }
+ os.MkdirAll(macos, 0777)
+ os.MkdirAll(resources, 0777)
+ if err := cp(binary, filepath.Join(macos, "kanban")); err != nil {
+ return fmt.Errorf("copying binary: %w", err)
+ }
+ if err := cp(plist, filepath.Join(contents, "Info.plist")); err != nil {
+ return fmt.Errorf("copying plist: %w", err)
+ }
+ if err := convertIcon(icon, filepath.Join(resources, "kanban.icns")); err != nil {
+ return fmt.Errorf("converting icon to icns: %w", err)
+ }
+ return nil
+}
+
+// convertIcon converts the source png to icon and returns a path to it.
+func convertIcon(src, dst string) error {
+ srcf, err := os.Open(src)
+ if err != nil {
+ return fmt.Errorf("opening source file: %w", err)
+ }
+ defer srcf.Close()
+ img, err := png.Decode(srcf)
+ if err != nil {
+ return fmt.Errorf("decoding source png: %w", err)
+ }
+ dstf, err := os.OpenFile(dst, os.O_CREATE|os.O_RDWR, 0644)
+ if err != nil {
+ return fmt.Errorf("opening destination file: %w", err)
+ }
+ defer dstf.Close()
+ if err := icns.Encode(dstf, img); err != nil {
+ return fmt.Errorf("encoding icns: %w", err)
+ }
+ return nil
+}
+
+// build the Go program rooted at path and returns a path to it.
+func build(path string) (string, error) {
+ path, err := filepath.Abs(path)
+ if err != nil {
+ return "", fmt.Errorf("resolving absolute path: %w", err)
+ }
+ if err := run("go", "build", "-o", "dist/kanban", path); err != nil {
+ return "", err
+ }
+ return "dist/kanban", nil
+}
+
+// run the specified command and return any error.
+func run(cmd string, args ...string) error {
+ if out, err := exec.Command(cmd, args...).CombinedOutput(); err != nil {
+ return fmt.Errorf("running command %q: %v: %w", cmd, string(out), err)
+ }
+ return nil
+}
+
+// cp copies src file to destination.
+// If destination is a directory, the file will be copied into it.
+// If destination doesn't exist it will be created as a file.
+// If destination is a file an error will be returned.
+func cp(src, dst string) error {
+ if src == "" || dst == "" {
+ return nil
+ }
+ var err error
+ src, err = filepath.Abs(src)
+ if err != nil {
+ return fmt.Errorf("resolving path: %w", err)
+ }
+ dst, err = filepath.Abs(dst)
+ if err != nil {
+ return fmt.Errorf("resolving path: %w", err)
+ }
+ srcf, err := os.Open(src)
+ if err != nil {
+ return fmt.Errorf("opening %q: %w", src, err)
+ }
+ defer srcf.Close()
+ _, err = os.Stat(filepath.Dir(dst))
+ if os.IsNotExist(err) {
+ os.MkdirAll(filepath.Dir(dst), 0777)
+ }
+ dstf, err := os.OpenFile(dst, os.O_CREATE|os.O_RDWR, 0777)
+ if err != nil {
+ return fmt.Errorf("creating %q: %w", dst, err)
+ }
+ defer dstf.Close()
+ if _, err := io.Copy(dstf, srcf); err != nil {
+ return fmt.Errorf("copying data: %w", err)
+ }
+ return nil
+}
diff --git a/go.mod b/go.mod
@@ -6,6 +6,9 @@ require (
gioui.org v0.0.0-20201101161300-a55302a49867
git.sr.ht/~whereswaldon/materials v0.0.0-20201101220027-a518d42fd730
github.com/asdine/storm/v3 v3.2.1
+ github.com/jackmordaunt/icns v1.0.0
+ github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect
+ github.com/pkg/errors v0.9.1 // indirect
go.etcd.io/bbolt v1.3.5 // indirect
golang.org/x/exp v0.0.0-20200908183739-ae8ad444f925
golang.org/x/sys v0.0.0-20201101102859-da207088b7d1 // indirect
diff --git a/go.sum b/go.sum
@@ -20,11 +20,17 @@ github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/jackmordaunt/icns v1.0.0 h1:RYSxplerf/l/DUd09AHtITwckkv/mqjVv4DjYdPmAMQ=
+github.com/jackmordaunt/icns v1.0.0/go.mod h1:7TTQVEuGzVVfOPPlLNHJIkzA6CoV7aH1Dv9dW351oOo=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
+github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
+github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
diff --git a/res/darwin/Info.plist b/res/darwin/Info.plist
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>en</string>
+ <key>CFBundleExecutable</key>
+ <string>kanban</string>
+ <key>CFBundleIdentifier</key>
+ <string>Kanban</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundleName</key>
+ <string>Kanban</string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+ <key>CFBundleShortVersionString</key>
+ <string>0.5.0</string>
+ <key>CFBundleSupportedPlatforms</key>
+ <array>
+ <string>MacOSX</string>
+ </array>
+ <key>CFBundleVersion</key>
+ <string>1</string>
+ <key>CFBundleIconFile</key>
+ <string>kanban.icns</string>
+ <key>NSHighResolutionCapable</key>
+ <true/>
+ <key>NSMainNibFile</key>
+ <string></string>
+ <key>NSSupportsAutomaticGraphicsSwitching</key>
+ <true/>
+ <key>CFBundleDisplayName</key>
+ <string>Kanban</string>
+</dict>
+</plist>
diff --git a/res/icon.png b/res/icon.png
Binary files differ.