kanban

Kanban client in Gio
Log | Files | Refs | README | LICENSE

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:
Acmd/release/main.go | 146+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mgo.mod | 3+++
Mgo.sum | 6++++++
Ares/darwin/Info.plist | 36++++++++++++++++++++++++++++++++++++
Ares/icon.png | 0
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.