-
Notifications
You must be signed in to change notification settings - Fork 18
Expand file tree
/
Copy pathMakefile
More file actions
617 lines (591 loc) · 29.7 KB
/
Copy pathMakefile
File metadata and controls
617 lines (591 loc) · 29.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
# chartr — build and checks.
#
# The portable baseline artifact is the pure-Go binary with the Svelte build
# embedded (ADR 0010, 0011). Linux also has supported desktop packages (ADRs
# 0011 and 0019). `make check` and `make test` run the pre-commit checks.
BIN := bin/chartr
.PHONY: build web go-build dev-backend dev-web check test vet clean \
webview bundle dmg appimage linux-packages snapshot release
## build: frontend then the self-contained binary with the SPA embedded.
build: web go-build
## web: install deps and produce web/dist (embedded by the web package).
web:
cd web && npm install && npm run build
go-build:
go build -o $(BIN) ./cmd/chartr
## dev-backend: run the chartr backend (serves :8787) for use behind `dev-web`.
##
## The `chartrdev` tag is what admits the Vite dev server's origin on the two
## websockets. Both handshakes are scoped to the address chartr is listening on,
## and Vite proxies with `changeOrigin: true` — the request arrives with the
## backend's Host and the browser's own Origin — so without the tag the dev proxy
## is refused like any other cross-origin page. The tag exists so that widening is
## a development build rather than a flag on a released binary: nothing else in
## this Makefile sets it, and `internal/server/origins_dev.go` is not compiled into
## anything that ships. Set CHARTR_DEV_ORIGIN if Vite landed somewhere other than
## localhost:5173.
##
## Run chartr any other way — `make build`, `go run ./cmd/chartr` — and you get the
## shipped behaviour, which is what `bin/chartr` serving the embedded SPA needs.
dev-backend:
go run -tags chartrdev ./cmd/chartr
## dev-web: run Vite with HMR, proxying /api and /ws to the backend.
dev-web:
cd web && npm run dev
## check: static checks — go vet and svelte-check.
check: vet
cd web && npm run check
vet:
go vet ./...
## test: the process-boundary suite. Runs standalone — the embedded dist needs
## only the committed .gitkeep to compile, and the tests drive the control
## socket and HTTP, not the built SPA.
test:
go test ./...
## snapshot: build the supported binaries locally without publishing, exactly as
## a release would (goreleaser, cgo-free), into build/goreleaser. Useful for
## eyeballing the artifact set before tagging.
snapshot:
go run github.com/goreleaser/goreleaser/v2@latest release --snapshot --clean
## release: cut the real release from the current tag (goreleaser). Runs in CI on
## a v* tag; needs GITHUB_TOKEN. Local use is for dry-runs — prefer `snapshot`.
release:
go run github.com/goreleaser/goreleaser/v2@latest release --clean
# The shell rides the same tag as the supported binary and must report the same
# stamp (ADR 0013), but it is built outside goreleaser, so the stamp is derived
# here. Overridable so CI can pass the exact tag it released.
WEBVIEW_VERSION ?= $(shell git describe --tags --always --dirty 2>/dev/null || echo dev)
WEBVIEW_COMMIT ?= $(shell git rev-parse HEAD 2>/dev/null || echo none)
WEBVIEW_DATE ?= $(shell git show -s --format=%cI HEAD 2>/dev/null || echo unknown)
# nFPM turns the already-built native shell into distro packages; pin it so a
# tag rebuild cannot silently change package bytes or metadata underneath us.
NFPM_VERSION ?= v2.47.0
## webview: build the native webview shell for the host into build/shell, with
## a per-asset .sha256 sidecar.
##
## It needs cgo and a system webview library and builds natively — cgo does not
## cross-compile — so the release workflow runs this once per runner. macOS and
## Windows consume it on the best-effort tier; Linux consumes it into the gated
## AppImage, deb and rpm artifacts (ADRs 0011 and 0019).
## The sidecar is deliberately per-asset: the supported release owns
## checksums.txt, and a best-effort artifact must never mutate that manifest.
##
## Linux needs one shim to build at all. webview_go's cgo line pins
## `pkg-config: gtk+-3.0 webkit2gtk-4.0`, and webkit2gtk-4.0 is gone from every
## current distro — Ubuntu 24.04, Debian trixie and Fedora 40+ ship 4.1 only, so
## the pin fails to resolve and the shell has never once built on Linux. The pin
## is the only thing that is 4.0-specific: the two releases share the
## `webkit2/webkit2.h` API (they differ in libsoup2 vs libsoup3, below the
## header), and webview_go's own vendored webview.h already prefers
## `libwebkit2gtk-4.1.so` at runtime. So a generated .pc that forwards 4.0 to 4.1
## is enough, and is why this does not need a fork of webview_go.
##
## It is generated rather than checked in so the version it advertises is the
## installed 4.1's real one, and only when 4.0 is genuinely absent — a distro
## that still has 4.0 resolves the pin itself and is left alone.
webview:
@set -e; \
goos=$$(go env GOOS); goarch=$$(go env GOARCH); \
if [ -n "$$GOOS" ] && [ "$$GOOS" != "$$goos" ]; then \
echo "webview shell cannot cross-compile to $$GOOS from $$goos (cgo); nothing to attach"; \
exit 0; \
fi; \
mkdir -p build/shell; \
if [ "$$goos" = "linux" ] && ! pkg-config --exists webkit2gtk-4.0 \
&& pkg-config --exists webkit2gtk-4.1; then \
wkver=$$(pkg-config --modversion webkit2gtk-4.1); \
mkdir -p build/pkgconfig; \
printf '%s\n' \
"# Generated by \`make webview\`; see that target's comment." \
"Name: webkit2gtk-4.0" \
"Description: Forwards webview_go's webkit2gtk-4.0 pin onto the installed 4.1" \
"Version: $$wkver" \
"Requires: webkit2gtk-4.1" \
> build/pkgconfig/webkit2gtk-4.0.pc; \
echo "webkit2gtk-4.0 absent; forwarding webview_go's pin onto 4.1 ($$wkver)"; \
PKG_CONFIG_PATH="$(CURDIR)/build/pkgconfig:$$PKG_CONFIG_PATH"; \
export PKG_CONFIG_PATH; \
fi; \
ext=""; [ "$$goos" = "windows" ] && ext=".exe"; \
name="chartr-shell_$(WEBVIEW_VERSION)_$${goos}_$${goarch}$$ext"; \
echo "building native webview shell for $${goos}/$${goarch}"; \
CGO_ENABLED=1 go build -tags webview -trimpath \
-ldflags "-s -w -X main.version=$(WEBVIEW_VERSION) -X main.commit=$(WEBVIEW_COMMIT) -X main.date=$(WEBVIEW_DATE)" \
-o "build/shell/$$name" ./cmd/webview; \
cd build/shell; \
if command -v sha256sum >/dev/null 2>&1; then \
sha256sum "$$name" > "$$name.sha256"; \
else \
shasum -a 256 "$$name" > "$$name.sha256"; \
fi; \
echo "built build/shell/$$name"
# The bundle's reverse-DNS identifier, read off the repo rather than invented.
MACAPP_ID := io.github.rengwu.chartr
# The oldest macOS the bundle claims to run on, used only if the executable turns
# out not to declare one. LSMinimumSystemVersion is otherwise read off the linked
# binary's own LC_BUILD_VERSION: the floor is whatever the runner's toolchain
# targeted, not a number we picked, and claiming an older one would only promise
# an operator a launch the loader then refuses.
MACAPP_MACOS := 12.0
# The marks the cockpit already ships (Vite copies web/public to the dist root, so
# these are the same bytes the runtime Dock icon reads).
#
# These are the mac-specific masters, NOT the square icon-512.png the PWA uses,
# and the difference is load-bearing: macOS does not mask app icons, so the .icns
# has to carry Apple's own shape — art inset to 824/1024 with a continuous-corner
# squircle and the template's shadow (ADR 0016). Feeding it a full-bleed square
# draws a tile that is visibly oversized and the wrong silhouette beside every
# neighbour in the Dock.
#
# Three of them, because the artwork is redrawn per size band rather than scaled:
# at 16 the cursor is bigger and bleeds off the plate, at 32 it is contained, and
# 48-and-up is the full-detail drawing. Each entry below is rendered from the
# master drawn for its band, and every canvas is the largest entry that band
# feeds, so the iconset loop only ever downscales. Regenerate all three with
# scripts/mac-app-icon.py.
MACAPP_MARK_16 := web/public/icon-mac-16.png
MACAPP_MARK_32 := web/public/icon-mac-32.png
MACAPP_MARK := web/public/icon-mac-1024.png
## bundle: assemble the best-effort macOS app bundle — the shell executable, an
## Info.plist and a generated icon — into build/macapp, ad-hoc signed.
##
## This is `make webview` plus packaging, not a new kind of build: it packages the
## shell that target just built, with the same stamp, for the host's own
## architecture (cgo does not cross-compile) — which is in the staged directory's
## name, so an operator can see what they are getting and a second architecture
## can appear beside it later without renaming this one. Run `make web` first: the
## shell serves the cockpit out of the embedded dist, exactly as the loose one does.
##
## The signature is ad-hoc (`-`), which is the minimum that makes the app launch —
## Apple Silicon refuses to execute a binary carrying no signature at all. It is
## NOT a Developer ID signature and the app is not notarized, so Gatekeeper blocks
## the first launch of a downloaded copy; that is a stated cost (ADR 0016), not a
## bug. Signing is last, after the property list and the icon are in place: the Go
## linker signs the executable it produces, but nothing signs the bundle around it.
##
## Off macOS this prints a line and succeeds, like the webview target it builds
## on, which is what lets the shells job stay green on every runner.
bundle:
@set -e; \
goos=$$(go env GOOS); \
if [ "$$goos" != "darwin" ]; then \
echo "the macOS app bundle is only assembled on macOS (this is $$goos); nothing to package"; \
exit 0; \
fi; \
$(MAKE) webview WEBVIEW_VERSION=$(WEBVIEW_VERSION) \
WEBVIEW_COMMIT=$(WEBVIEW_COMMIT) WEBVIEW_DATE=$(WEBVIEW_DATE); \
goarch=$$(go env GOARCH); \
exe="build/shell/chartr-shell_$(WEBVIEW_VERSION)_darwin_$${goarch}"; \
stage="build/macapp/chartr_$(WEBVIEW_VERSION)_darwin_$${goarch}"; \
app="$$stage/chartr.app"; \
echo "assembling $$app"; \
rm -rf "$$stage"; \
mkdir -p "$$app/Contents/MacOS" "$$app/Contents/Resources"; \
cp "$$exe" "$$app/Contents/MacOS/chartr"; \
iconset="$$stage/chartr.iconset"; \
mkdir -p "$$iconset"; \
for spec in "16 icon_16x16 $(MACAPP_MARK_16)" \
"32 icon_16x16@2x $(MACAPP_MARK_32)" \
"32 icon_32x32 $(MACAPP_MARK_32)" \
"64 icon_32x32@2x $(MACAPP_MARK)" \
"128 icon_128x128 $(MACAPP_MARK)" \
"256 icon_128x128@2x $(MACAPP_MARK)" \
"256 icon_256x256 $(MACAPP_MARK)" \
"512 icon_256x256@2x $(MACAPP_MARK)" \
"512 icon_512x512 $(MACAPP_MARK)"; do \
set -- $$spec; \
sips -z "$$1" "$$1" "$$3" --out "$$iconset/$$2.png" >/dev/null; \
done; \
iconutil -c icns "$$iconset" -o "$$app/Contents/Resources/chartr.icns"; \
rm -rf "$$iconset"; \
short=$$(printf '%s' '$(WEBVIEW_VERSION)' \
| sed -n 's/^v\{0,1\}\([0-9][0-9]*\(\.[0-9][0-9]*\)\{0,2\}\)\([-+].*\)\{0,1\}$$/\1/p'); \
[ -n "$$short" ] || short=0.0.0; \
minos=$$(otool -l "$$app/Contents/MacOS/chartr" 2>/dev/null \
| awk '/minos/ { print $$2; exit }'); \
[ -n "$$minos" ] || minos=$(MACAPP_MACOS); \
year=$$(printf '%s' '$(WEBVIEW_DATE)' | sed -n 's/^\([0-9]\{4\}\).*/\1/p'); \
[ -n "$$year" ] || year=$$(date +%Y); \
printf '%s\n' \
'<?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>CFBundleInfoDictionaryVersion</key><string>6.0</string>' \
' <key>CFBundlePackageType</key><string>APPL</string>' \
' <key>CFBundleName</key><string>chartr</string>' \
' <key>CFBundleDisplayName</key><string>chartr</string>' \
' <key>CFBundleIdentifier</key><string>$(MACAPP_ID)</string>' \
' <key>CFBundleExecutable</key><string>chartr</string>' \
' <key>CFBundleIconFile</key><string>chartr</string>' \
" <key>CFBundleShortVersionString</key><string>$$short</string>" \
' <key>CFBundleVersion</key><string>$(WEBVIEW_VERSION)</string>' \
" <key>NSHumanReadableCopyright</key><string>Copyright © $$year John Goh Rengwu. All rights reserved.</string>" \
" <key>LSMinimumSystemVersion</key><string>$$minos</string>" \
' <key>LSApplicationCategoryType</key><string>public.app-category.developer-tools</string>' \
' <key>NSHighResolutionCapable</key><true/>' \
'</dict>' \
'</plist>' \
> "$$app/Contents/Info.plist"; \
codesign --force --sign - --identifier "$(MACAPP_ID)" "$$app"; \
codesign --verify --strict "$$app"; \
echo "built $$app (version $$short, build $(WEBVIEW_VERSION), darwin/$$goarch, macOS $$minos+, ad-hoc signed)"
## dmg: stage the assembled bundle into the one file an operator downloads — a
## read-only disk image in build/macapp, with a per-asset .sha256 sidecar.
##
## The staged layout is the customary one: the app, a symlink to /Applications as
## the drag target, and a plain-text file carrying the Gatekeeper instructions.
## There is deliberately NO styled window — background art with positioned icons
## means scripting the Finder and committing a window-state file, which is
## cosmetics on a tier that ships with a "your Mac will block this" note in the
## box (ADR 0016).
##
## The architecture in the image's name is load-bearing, not decoration: the
## bundle is one slice (cgo does not cross-compile), so the name is what tells an
## operator whether the image is theirs, and what lets a second architecture
## appear beside it later without renaming this one.
##
## The sidecar is per-asset for the same reason `make webview` writes one: the
## supported release owns checksums.txt, and a best-effort artifact never mutates
## that manifest.
##
## Off macOS this prints a line and succeeds, like the bundle target it builds on.
dmg:
@set -e; \
goos=$$(go env GOOS); \
if [ "$$goos" != "darwin" ]; then \
echo "the macOS disk image is only built on macOS (this is $$goos); nothing to package"; \
exit 0; \
fi; \
$(MAKE) bundle WEBVIEW_VERSION=$(WEBVIEW_VERSION) \
WEBVIEW_COMMIT=$(WEBVIEW_COMMIT) WEBVIEW_DATE=$(WEBVIEW_DATE); \
goarch=$$(go env GOARCH); \
name="chartr_$(WEBVIEW_VERSION)_darwin_$${goarch}"; \
app="build/macapp/$$name/chartr.app"; \
out="build/macapp/$$name.dmg"; \
stage="build/macapp/.dmg-root"; \
short=$$(plutil -extract CFBundleShortVersionString raw "$$app/Contents/Info.plist"); \
echo "staging $$out"; \
rm -rf "$$stage" "$$out"; \
mkdir -p "$$stage"; \
ditto "$$app" "$$stage/chartr.app"; \
ln -s /Applications "$$stage/Applications"; \
printf '%s\n' \
'chartr for macOS' \
'================' \
'' \
'chartr is signed ad-hoc and is NOT notarized: this project has no Apple' \
'Developer account. So macOS blocks the first launch. That is expected, and' \
'the steps below are the whole of it.' \
'' \
'Install' \
'-------' \
'1. Drag chartr onto the Applications folder in this window.' \
'2. Eject this disk image.' \
'' \
'First launch (the one macOS blocks)' \
'-----------------------------------' \
'3. Open chartr from Launchpad or the Applications folder. macOS puts up a' \
' dialog: "chartr" Not Opened - Apple could not verify "chartr" is free of' \
' malware that may harm your Mac or compromise your privacy.' \
' Click Done. Do NOT click Move to Trash, which is the highlighted button.' \
'4. Open System Settings > Privacy & Security and scroll down to Security.' \
' Under the line "chartr" was blocked to protect your Mac, click' \
' Open Anyway. Authenticate with Touch ID or your password, then click' \
' Open Anyway once more in the dialog that follows.' \
'' \
'chartr opens, and every later launch opens with no prompt at all.' \
'' \
'These steps were verified on macOS 27.0. Right-clicking the app and choosing' \
'Open does NOT clear this any more, whatever older advice says - use step 4.' \
'' \
'From a terminal instead, if you prefer:' \
' xattr -d com.apple.quarantine /Applications/chartr.app' \
'That removes the quarantine attribute macOS attaches to downloads. Run it' \
'only if you trust this download.' \
'' \
'What is in this image' \
'---------------------' \
"chartr $$short (build $(WEBVIEW_VERSION)), for $$goarch Macs only." \
'Verify the download against the sidecar published beside it:' \
" shasum -a 256 -c $$name.dmg.sha256" \
> "$$stage/READ ME FIRST.txt"; \
hdiutil create -quiet -volname "chartr $$short" -srcfolder "$$stage" \
-fs HFS+ -format UDZO -ov "$$out"; \
rm -rf "$$stage"; \
( cd build/macapp && shasum -a 256 "$$name.dmg" > "$$name.dmg.sha256" ); \
echo "built $$out (+ .sha256) — chartr $$short, darwin/$$goarch, unsigned and un-notarized"
# The two AppImage tools, pinned. They are fetched at package time, so a rolling
# `continuous` tag would make a release's contents a function of the day it was
# cut rather than of this commit. linuxdeploy publishes only dated alphas; that
# is the newest, and moving it is a deliberate edit like any other dependency
# bump.
LINUXDEPLOY_VERSION := 1-alpha-20251107-1
APPIMAGETOOL_VERSION := 1.9.1
# ...and pinned by content, not only by tag, because a tag is not a pin.
#
# `1-alpha-20251107-1` is a GitHub release tag, and a release tag is mutable: the
# publisher can re-point it at different bytes without the version string here
# changing. linuxdeploy publishes nothing but dated alphas, so there is no stable
# tag to move to — the checksum below is what makes depending on that tag safe.
# appimagetool's 1.9.1 is an ordinary release and is better, but it is mutable in
# exactly the same way and gets the same treatment.
#
# Both tools are executed on the build machine and shape the artifact shipped to
# users, so they are verified after download and BEFORE chmod +x, on the cached
# path as well as the freshly fetched one. A mismatch deletes the file and fails
# the target; it is never a warning.
#
# To bump either tool:
#
# 1. Change the version above.
# 2. Re-derive BOTH architectures' checksums — they change together, and a
# stale aarch64 sum will not show up on an amd64 build machine:
#
# v=1-alpha-20251107-1 # the new tag
# for a in x86_64 aarch64; do \
# curl -fsSL -o /tmp/t \
# "https://github.com/linuxdeploy/linuxdeploy/releases/download/$v/linuxdeploy-$a.AppImage"; \
# echo "$a $(sha256sum /tmp/t | cut -d' ' -f1)"; \
# done
#
# (appimagetool is the same shape against AppImage/appimagetool and
# appimagetool-$a.AppImage.)
# 3. Cross-check each against the digest GitHub reports for the asset itself,
# which is computed server-side and so is not just your own download read
# back to you:
#
# curl -fsSL https://api.github.com/repos/linuxdeploy/linuxdeploy/releases/tags/$v \
# | python3 -c "import json,sys; [print(a['name'], a.get('digest')) for a in json.load(sys.stdin)['assets']]"
#
# The four values recorded below were derived and cross-checked this way on
# 2026-08-03; local sums and API digests agreed.
# 4. Delete build/appimage/tools so the next build re-fetches rather than
# verifying the old binary against the new sum and failing.
LINUXDEPLOY_SHA256_x86_64 := c20cd71e3a4e3b80c3483cef793cda3f4e990aca14014d23c544ca3ce1270b4d
LINUXDEPLOY_SHA256_aarch64 := 620095110d693282b8ebeb244a95b5e911cf8f65f76c88b4b47d16ae6346fcff
APPIMAGETOOL_SHA256_x86_64 := ed4ce84f0d9caff66f50bcca6ff6f35aae54ce8135408b3fa33abfc3cb384eb0
APPIMAGETOOL_SHA256_aarch64 := f0837e7448a0c1e4e650a93bb3e85802546e60654ef287576f46c71c126a9158
# Linux desktops mask, round and theme icons themselves, and expect a full-bleed
# square to do it to — so this is the PWA master, deliberately NOT the macOS
# squircle set the bundle uses (ADR 0016). Feeding a Linux desktop Apple's
# pre-inset, pre-shadowed art draws a small tile floating in a transparent box.
APPIMAGE_MARK := web/public/icon-512.png
# Libraries linuxdeploy excludes by default that we put back.
#
# AppImage ships an excludelist of libraries every AppImage is expected to
# borrow from the host rather than carry, and most of it is right for good
# reasons: libEGL belongs to the installed GPU driver, libfontconfig has to see
# the operator's own font configuration, and libwayland-client speaks a protocol
# to their compositor. Bundling any of those three substitutes our build
# machine's idea of their hardware, their fonts or their session — the bundled
# copy is not merely redundant, it is wrong.
#
# These two are on that list for neither reason. Text shaping and bidi ordering
# are pure computation over bytes we hand in, coupled to nothing outside the
# process, and they are the two entries that a minimal or non-desktop-oriented
# distro is genuinely liable not to have — which would leave the operator with a
# dynamic-linker error rather than a window. So they come with us, and the other
# three stay the host's.
APPIMAGE_FORCE_LIBS := libharfbuzz.so.0 libfribidi.so.0
# Where the bundled WebKit is told to find its helper processes. WebKitGTK 2.52
# dropped the WEBKIT_EXEC_PATH override, so this path is written into the
# bundled library itself (scripts/relocate-webkit.py) and pointed at the AppDir
# by packaging/linux/AppRun, which hardcodes the same literal — change one and
# you must change the other, or the window renders a WebKit error page.
# It is shorter than the /usr/lib/<triplet>/webkit2gtk-4.1 it replaces because
# the edit is made in place, and the AppRun refuses to reuse it unless the
# symlink already there belongs to the operator running it.
APPIMAGE_WK_LINK := /tmp/.chartr-wk
## appimage: package the Linux webview shell as a self-contained AppImage into
## build/appimage, with a per-asset .sha256 sidecar.
##
## This is `make webview` plus packaging, the way `make dmg` is on macOS — and
## like it, it runs only on its own platform and prints-and-succeeds elsewhere.
##
## What it bundles is WebKitGTK, and that is the entire difficulty. A distro
## either ships webkit2gtk-4.1 (Ubuntu 24.04+, Fedora 40+, Arch), or 4.0 with an
## incompatible libsoup2 underneath (Ubuntu 22.04, Debian 12), or neither — so a
## shell linked against the build machine's WebKit runs on roughly one third of
## the desktops it is handed to. Bundling it is what makes one artifact run
## everywhere, which is the whole reason this is an AppImage and not a tarball.
##
## WebKit is not a library you can bundle by copying a .so, though: it spawns
## helper executables and loads an injected bundle, each found through a path
## compiled into the library. Those are staged by hand below, and packaging/linux/AppRun
## redirects the lookups at runtime; linuxdeploy is then pointed at the staged
## directories with --deploy-deps-only so the helpers' own dependencies are
## walked and their rpaths patched exactly like the main executable's.
appimage:
@set -e; \
goos=$$(go env GOOS); goarch=$$(go env GOARCH); \
if [ "$$goos" != "linux" ]; then \
echo "the AppImage is only packaged on Linux (this is $$goos); nothing to package"; \
exit 0; \
fi; \
case "$$goarch" in \
amd64) tool_arch=x86_64; \
linuxdeploy_sha="$(LINUXDEPLOY_SHA256_x86_64)"; \
appimagetool_sha="$(APPIMAGETOOL_SHA256_x86_64)" ;; \
arm64) tool_arch=aarch64; \
linuxdeploy_sha="$(LINUXDEPLOY_SHA256_aarch64)"; \
appimagetool_sha="$(APPIMAGETOOL_SHA256_aarch64)" ;; \
*) echo "no AppImage tooling for linux/$$goarch; nothing to package"; exit 0 ;; \
esac; \
verify_tool() { \
if command -v sha256sum >/dev/null 2>&1; then \
actual=$$(sha256sum "$$1" | cut -d' ' -f1); \
else \
actual=$$(shasum -a 256 "$$1" | cut -d' ' -f1); \
fi; \
if [ "$$actual" != "$$2" ]; then \
rm -f "$$1"; \
echo "$$1: SHA-256 mismatch — refusing to run it" >&2; \
echo " expected $$2" >&2; \
echo " actual $$actual" >&2; \
echo " the file has been deleted. Either the release tag was re-pointed at" >&2; \
echo " different bytes, or the download was tampered with. Do not bypass this:" >&2; \
echo " re-derive the checksum only after establishing why it changed (Makefile," >&2; \
echo " beside LINUXDEPLOY_VERSION)." >&2; \
exit 1; \
fi; \
}; \
$(MAKE) webview WEBVIEW_VERSION=$(WEBVIEW_VERSION) \
WEBVIEW_COMMIT=$(WEBVIEW_COMMIT) WEBVIEW_DATE=$(WEBVIEW_DATE); \
shell_bin="build/shell/chartr-shell_$(WEBVIEW_VERSION)_linux_$$goarch"; \
if [ ! -x "$$shell_bin" ]; then \
echo "no shell binary at $$shell_bin — nothing to package"; \
exit 0; \
fi; \
triplet=$$(gcc -print-multiarch 2>/dev/null || echo "$$(uname -m)-linux-gnu"); \
libdir="/usr/lib/$$triplet"; \
tools="build/appimage/tools"; \
mkdir -p "$$tools"; \
if [ ! -x "$$tools/linuxdeploy" ]; then \
echo "fetching linuxdeploy $(LINUXDEPLOY_VERSION) ($$tool_arch)"; \
curl -fsSL -o "$$tools/linuxdeploy" \
"https://github.com/linuxdeploy/linuxdeploy/releases/download/$(LINUXDEPLOY_VERSION)/linuxdeploy-$$tool_arch.AppImage"; \
fi; \
verify_tool "$$tools/linuxdeploy" "$$linuxdeploy_sha"; \
chmod +x "$$tools/linuxdeploy"; \
if [ ! -x "$$tools/appimagetool" ]; then \
echo "fetching appimagetool $(APPIMAGETOOL_VERSION) ($$tool_arch)"; \
curl -fsSL -o "$$tools/appimagetool" \
"https://github.com/AppImage/appimagetool/releases/download/$(APPIMAGETOOL_VERSION)/appimagetool-$$tool_arch.AppImage"; \
fi; \
verify_tool "$$tools/appimagetool" "$$appimagetool_sha"; \
chmod +x "$$tools/appimagetool"; \
appdir="build/appimage/AppDir"; \
rm -rf "$$appdir"; \
mkdir -p "$$appdir/usr/bin" "$$appdir/usr/lib"; \
cp "$$shell_bin" "$$appdir/usr/bin/chartr"; \
chmod +x "$$appdir/usr/bin/chartr"; \
if [ -d "$$libdir/webkit2gtk-4.1" ]; then \
echo "staging WebKitGTK helper processes from $$libdir/webkit2gtk-4.1"; \
mkdir -p "$$appdir/usr/lib/webkit2gtk-4.1/injected-bundle"; \
for helper in WebKitNetworkProcess WebKitWebProcess WebKitGPUProcess; do \
[ -f "$$libdir/webkit2gtk-4.1/$$helper" ] && \
cp "$$libdir/webkit2gtk-4.1/$$helper" "$$appdir/usr/lib/webkit2gtk-4.1/"; \
done; \
cp "$$libdir/webkit2gtk-4.1/injected-bundle/"*.so \
"$$appdir/usr/lib/webkit2gtk-4.1/injected-bundle/" 2>/dev/null || true; \
else \
echo "::warning:: no $$libdir/webkit2gtk-4.1 — the window will open blank"; \
fi; \
if [ -d "$$libdir/gio/modules" ]; then \
mkdir -p "$$appdir/usr/lib/gio/modules"; \
cp "$$libdir/gio/modules/"*.so "$$appdir/usr/lib/gio/modules/" 2>/dev/null || true; \
fi; \
if [ -d "$$libdir/gdk-pixbuf-2.0" ]; then \
mkdir -p "$$appdir/usr/lib/gdk-pixbuf-2.0/2.10.0"; \
cp -r "$$libdir/gdk-pixbuf-2.0/2.10.0/loaders" \
"$$appdir/usr/lib/gdk-pixbuf-2.0/2.10.0/" 2>/dev/null || true; \
cp "$$libdir/gdk-pixbuf-2.0/gdk-pixbuf-query-loaders" \
"$$appdir/usr/lib/gdk-pixbuf-2.0/" 2>/dev/null || true; \
fi; \
if [ -f /usr/share/glib-2.0/schemas/gschemas.compiled ]; then \
mkdir -p "$$appdir/usr/share/glib-2.0/schemas"; \
cp /usr/share/glib-2.0/schemas/gschemas.compiled \
"$$appdir/usr/share/glib-2.0/schemas/"; \
else \
echo "::warning:: no compiled GSettings schemas on this machine"; \
exit 1; \
fi; \
deps=""; \
for d in "$$appdir/usr/lib/webkit2gtk-4.1" "$$appdir/usr/lib/gio/modules" \
"$$appdir/usr/lib/gdk-pixbuf-2.0/2.10.0/loaders"; do \
[ -d "$$d" ] && deps="$$deps --deploy-deps-only=$$d"; \
done; \
libs=""; \
for l in $(APPIMAGE_FORCE_LIBS); do \
[ -f "$$libdir/$$l" ] && libs="$$libs --library=$$libdir/$$l"; \
done; \
echo "deploying dependencies into $$appdir"; \
env -u LD_LIBRARY_PATH APPIMAGE_EXTRACT_AND_RUN=1 "$$tools/linuxdeploy" \
--appdir "$$appdir" \
--executable "$$appdir/usr/bin/chartr" \
--desktop-file packaging/linux/chartr.desktop \
--icon-file $(APPIMAGE_MARK) \
--icon-filename chartr \
--custom-apprun packaging/linux/AppRun \
$$deps $$libs; \
python3 scripts/relocate-webkit.py \
"$$appdir/usr/lib/libwebkit2gtk-4.1.so.0" \
"$$libdir/webkit2gtk-4.1" \
"$(APPIMAGE_WK_LINK)"; \
short=$$(echo "$(WEBVIEW_VERSION)" | sed 's/^v//'); \
name="chartr_$${short}_linux_$$goarch"; \
out="build/appimage/$$name.AppImage"; \
rm -f "$$out"; \
echo "building $$out"; \
env -u LD_LIBRARY_PATH APPIMAGE_EXTRACT_AND_RUN=1 \
ARCH=$$tool_arch VERSION="$$short" \
"$$tools/appimagetool" --no-appstream "$$appdir" "$$out"; \
( cd build/appimage && sha256sum "$$name.AppImage" > "$$name.AppImage.sha256" ); \
echo "built $$out (+ .sha256) — chartr $$short, linux/$$goarch, WebKitGTK bundled"
## linux-packages: package the Linux webview shell as native deb and rpm files
## into build/packages, each with a per-asset .sha256 sidecar.
##
## Unlike the AppImage, these packages do not bundle GTK or WebKitGTK. apt/dnf
## installs the platform's WebKitGTK 4.1 runtime from the dependency metadata in
## packaging/linux/nfpm.yaml, so distro security updates service the renderer.
## The binary is still built natively because it uses cgo; the release workflow
## runs this target once on each Linux architecture it publishes.
linux-packages:
@set -e; \
goos=$$(go env GOOS); goarch=$$(go env GOARCH); \
if [ "$$goos" != "linux" ]; then \
echo "deb/rpm packages are only assembled on Linux (this is $$goos); nothing to package"; \
exit 0; \
fi; \
$(MAKE) webview WEBVIEW_VERSION=$(WEBVIEW_VERSION) \
WEBVIEW_COMMIT=$(WEBVIEW_COMMIT) WEBVIEW_DATE=$(WEBVIEW_DATE); \
binary="build/shell/chartr-shell_$(WEBVIEW_VERSION)_linux_$${goarch}"; \
if [ ! -x "$$binary" ]; then \
echo "no shell binary at $$binary — nothing to package"; \
exit 0; \
fi; \
version=$$(printf '%s' '$(WEBVIEW_VERSION)' | sed 's/^v//'); \
case "$$version" in \
[0-9]*) ;; \
*) echo "native package version must start with a digit (got $$version)" >&2; exit 1 ;; \
esac; \
outdir="build/packages"; \
mkdir -p "$$outdir"; \
if [ -z "$${SOURCE_DATE_EPOCH:-}" ]; then \
SOURCE_DATE_EPOCH=$$(git show -s --format=%ct HEAD 2>/dev/null || date +%s); \
fi; \
export SOURCE_DATE_EPOCH; \
PACKAGE_VERSION="$$version"; PACKAGE_ARCH="$$goarch"; PACKAGE_BINARY="$$binary"; \
export PACKAGE_VERSION PACKAGE_ARCH PACKAGE_BINARY; \
for format in deb rpm; do \
out="$$outdir/chartr_$${version}_linux_$${goarch}.$${format}"; \
rm -f "$$out" "$$out.sha256"; \
echo "packaging $$out (system WebKitGTK)"; \
go run github.com/goreleaser/nfpm/v2/cmd/nfpm@$(NFPM_VERSION) package \
--config packaging/linux/nfpm.yaml --packager "$$format" --target "$$out"; \
( cd "$$outdir" && sha256sum "$$(basename "$$out")" > "$$(basename "$$out").sha256" ); \
done; \
echo "built native Linux packages for $${goarch} (+ .sha256)"
clean:
rm -rf $(BIN) build/