From a8a60f58601957fdd4e72416aa1b00956c950d7c Mon Sep 17 00:00:00 2001 From: bsncubed Date: Fri, 31 Jul 2026 22:30:22 +1000 Subject: [PATCH] build: package haul as a macOS .app bundle MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Double-clicking the bare binary in Finder opens a Terminal window alongside the app: Finder has no better idea what to do with a Mach-O executable than hand it to Terminal.app, and the terminal stays up as long as the process does. macOS has no counterpart to -H=windowsgui, so the only fix is to ship an application bundle, which Finder launches directly. Add `make package-darwin`, which runs a pinned `fyne package --target darwin` to produce ./haul.app. Its metadata comes from cmd/haul/FyneApp.toml — the app ID there must stay in step with the one app.NewWithID uses, or macOS treats the bundle and the running app as separate applications. The icon is a placeholder; `fyne package` will not run without one. Note that `fyne package` rewrites FyneApp.toml in place on every run, so the file is left in that tool's canonical formatting rather than a commented version it would only strip again. Co-Authored-By: Claude Opus 5 --- .gitignore | 1 + Makefile | 24 +++++++++++++++++-- README.md | 52 ++++++++++++++++++++++++++++++++++++++++-- cmd/haul/FyneApp.toml | 14 ++++++++++++ cmd/haul/Icon.png | Bin 0 -> 6426 bytes 5 files changed, 87 insertions(+), 4 deletions(-) create mode 100644 cmd/haul/FyneApp.toml create mode 100644 cmd/haul/Icon.png diff --git a/.gitignore b/.gitignore index 4febd3b..d0b0bbc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /bin/ /dist/ +/haul.app/ diff --git a/Makefile b/Makefile index 41c6a96..867b288 100644 --- a/Makefile +++ b/Makefile @@ -7,7 +7,12 @@ LDFLAGS := -s -w # prefer that toolchain and fall back to whatever `go` is in PATH elsewhere. GO ?= $(shell test -x $(HOME)/.local/go/bin/go && echo $(HOME)/.local/go/bin/go || echo go) -.PHONY: build install test vet check run run-xvfb dist clean +# The packaging tool lives outside the Fyne library repo since v2.5. Pinned and +# run through `go run` so no separate install step is needed; override with +# FYNE=/path/to/fyne if you'd rather use an installed copy. +FYNE ?= $(GO) run fyne.io/tools/cmd/fyne@v1.7.2 + +.PHONY: build install test vet check run run-xvfb dist package-darwin clean build: ## Build a native binary into ./bin/haul $(GO) build -trimpath -ldflags "$(LDFLAGS)" -o bin/haul ./cmd/haul @@ -60,5 +65,20 @@ dist: ## Build the release binary for this host into ./dist $(GO) build -trimpath -ldflags "$(LDFLAGS)" -o dist/haul-linux-amd64 ./cmd/haul @echo "built dist/haul-linux-amd64 (see the Makefile for cross-compiling)" +# Finder hands a bare Unix executable to Terminal.app, so double-clicking the +# raw binary opens a terminal window alongside the GUI. Nothing in the code can +# stop that — the fix is to ship a .app bundle instead, which Finder launches +# directly. `fyne package` writes the Info.plist (CFBundlePackageType APPL and +# friends) and converts cmd/haul/Icon.png into Contents/Resources/icon.icns; the +# rest of the metadata comes from cmd/haul/FyneApp.toml. +# +# Run this ON a Mac: it compiles the binary with the host cgo toolchain, and it +# builds for the host architecture, so an Intel Mac gives an amd64 bundle and +# Apple silicon an arm64 one. The .app is unsigned, so first launch needs +# right-click -> Open (or `xattr -dr com.apple.quarantine haul.app`). +package-darwin: ## Package haul.app for macOS (run this on a Mac) + $(FYNE) package --target darwin --src ./cmd/haul --release + @echo "built ./haul.app — drag it into /Applications" + clean: ## Remove build artifacts - rm -rf bin dist + rm -rf bin dist haul.app diff --git a/README.md b/README.md index 783dda7..f5f9bc2 100644 --- a/README.md +++ b/README.md @@ -205,6 +205,51 @@ Unlike Linux, Windows needs no graphics development *packages* to build: OpenGL ships with the operating system. Getting a usable OpenGL at **run** time is a separate matter — see below. +### Packaging on macOS + +`make build` produces a plain Unix executable, and that is all you need if you +launch haul from a shell. **Double-click it in Finder, though, and macOS opens a +Terminal window alongside the app.** That is not a bug in haul and no build flag +fixes it — it is what Finder does with any bare executable: having no better +idea what a Mach-O file is, it hands it to Terminal.app to run, and the terminal +stays on screen for as long as the process does. macOS has no equivalent of +Windows' `-H=windowsgui`. + +The fix is to ship an application *bundle* — a `haul.app` directory carrying an +`Info.plist` — which Finder launches directly, with a Dock icon and no terminal: + +```sh +make package-darwin # -> ./haul.app +``` + +Then drag `haul.app` into `/Applications`. Run it on a Mac: it compiles with the +host cgo toolchain, and it builds for the host architecture, so an Intel Mac +gives you an `amd64` bundle and Apple silicon an `arm64` one. + +Under the hood that target runs [`fyne +package`](https://docs.fyne.io/started/packaging), which reads its metadata from +`cmd/haul/FyneApp.toml`, converts `cmd/haul/Icon.png` into the bundle's +`.icns` icon, and writes the `Info.plist`. Without `make`: + +```sh +go run fyne.io/tools/cmd/fyne@v1.7.2 package --target darwin --src ./cmd/haul --release +``` + +Note that `fyne package` **rewrites `cmd/haul/FyneApp.toml` in place**, bumping +`Build` on every run and dropping any comments you add to it. That is by design; +commit the bump or discard it as you prefer. + +The bundle is unsigned, so Gatekeeper blocks the first launch on any Mac that +did not build it. Right-click the app and choose **Open** (once — after that it +starts normally), or clear the quarantine flag: + +```sh +xattr -dr com.apple.quarantine /Applications/haul.app +``` + +Signing and notarising properly needs a paid Apple Developer ID, after which +`fyne release --target darwin --certificate ` handles it. + ### Running over Remote Desktop (or in a VM) A build that compiles perfectly will still fail to start in an RDP session with: @@ -269,7 +314,8 @@ Software rendering is slower than a GPU, but haul draws two tables and some text | `make run` | Build, then run (needs a `DISPLAY`) | | `make run-xvfb` | Build, then run against a virtual X display | | `make dist` | Release binary into `./dist` | -| `make clean` | Remove `bin/` and `dist/` | +| `make package-darwin` | macOS `.app` bundle into `./haul.app` (run on a Mac) | +| `make clean` | Remove `bin/`, `dist/` and `haul.app/` | The Makefile picks up a Go toolchain at `~/.local/go/bin/go` if one is there, falling back to whatever `go` is on `PATH` — so it works on machines where Go @@ -297,7 +343,9 @@ toolchain: | `windows/amd64` | `gcc-mingw-w64` | | `darwin/*` | a Mac, or [osxcross](https://github.com/tpoechtrager/osxcross) | -For that reason `make dist` builds only the native binary. The practical route +For that reason `make dist` builds only the native binary — and on macOS the +binary alone is not the artefact you want to hand out, see [Packaging on +macOS](#packaging-on-macos). The practical route to the others is [`fyne-cross`](https://github.com/fyne-io/fyne-cross), which does each build inside a Docker image carrying the right toolchain: diff --git a/cmd/haul/FyneApp.toml b/cmd/haul/FyneApp.toml new file mode 100644 index 0000000..850767d --- /dev/null +++ b/cmd/haul/FyneApp.toml @@ -0,0 +1,14 @@ +Website = "https://gitea.apointless.space/bsncubed/haul" + +[Details] +Icon = "Icon.png" +Name = "haul" +ID = "space.apointless.haul" +Version = "0.1.0" +Build = 1 + +[LinuxAndBSD] +GenericName = "SFTP Client" +Categories = ["Network", "FileTransfer", "Utility"] +Comment = "Two-pane SFTP file browser over SSH" +Keywords = ["sftp", "ssh", "scp", "file transfer"] diff --git a/cmd/haul/Icon.png b/cmd/haul/Icon.png new file mode 100644 index 0000000000000000000000000000000000000000..f401d813bd5303c9e0ff651dd59e399ee9872774 GIT binary patch literal 6426 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 literal 0 HcmV?d00001