1#!/usr/bin/env fish
2
3function __release_fork_usage
4 echo "Usage: release-fork.fish [--from <stage>] [--only <stage>] [--help]
5
6Stages (run in order):
7 tag Compute next fork tag, push branch, create and push tag
8 build Cross-compile for all release targets
9 pack Compress Linux binaries with UPX
10 upload Upload artifacts via release(1)
11
12Flags:
13 --from <stage> Start at this stage and continue through the rest
14 --only <stage> Run just this one stage
15 --help, -h Show this help
16
17With no flags, all stages run in order (tag → build → pack → upload).
18
19Examples:
20 ./release-fork.fish Full release
21 ./release-fork.fish --from build Rebuild, pack, and upload (skip tagging)
22 ./release-fork.fish --only build Just cross-compile
23 ./release-fork.fish --only upload Re-upload existing dist/ artifacts"
24end
25
26set -l targets \
27 linux/amd64 \
28 linux/arm64 \
29 darwin/amd64 \
30 darwin/arm64 \
31 windows/amd64 \
32 freebsd/amd64
33
34# UPX 5.x dropped macOS support; windows/amd64 is PE32 only and
35# freebsd/amd64 is ELF32 only, so only Linux targets are packed.
36set -l pack_targets \
37 linux-amd64 \
38 linux-arm64
39
40# Global so __should_run can see them. Prefixed to avoid collisions.
41set -g __rf_stages tag build pack upload
42set -g __rf_from ""
43set -g __rf_only ""
44
45# --- Parse flags ---
46
47set -l i 1
48while test $i -le (count $argv)
49 switch $argv[$i]
50 case --from
51 set i (math $i + 1)
52 set -g __rf_from $argv[$i]
53 case --only
54 set i (math $i + 1)
55 set -g __rf_only $argv[$i]
56 case --help -h
57 __release_fork_usage
58 exit 0
59 case '*'
60 echo "Error: unknown flag '$argv[$i]'" >&2
61 __release_fork_usage >&2
62 exit 1
63 end
64 set i (math $i + 1)
65end
66
67if test -n "$__rf_from" -a -n "$__rf_only"
68 echo "Error: --from and --only are mutually exclusive" >&2
69 exit 1
70end
71
72if test -n "$__rf_from"; and not contains -- "$__rf_from" $__rf_stages
73 echo "Error: unknown stage '$__rf_from' (valid: $__rf_stages)" >&2
74 exit 1
75end
76
77if test -n "$__rf_only"; and not contains -- "$__rf_only" $__rf_stages
78 echo "Error: unknown stage '$__rf_only' (valid: $__rf_stages)" >&2
79 exit 1
80end
81
82# --- Determine which stages to run ---
83
84# Returns 0 if the given stage should execute, 1 otherwise.
85function __should_run -a stage
86 if test -n "$__rf_only"
87 test "$stage" = "$__rf_only"
88 return
89 end
90
91 if test -z "$__rf_from"
92 return 0
93 end
94
95 # Walk the stage list; once we reach __rf_from, every subsequent
96 # stage (including __rf_from itself) should run.
97 set -l reached 0
98 for s in $__rf_stages
99 if test "$s" = "$__rf_from"
100 set reached 1
101 end
102 if test $reached -eq 1 -a "$s" = "$stage"
103 return 0
104 end
105 end
106 return 1
107end
108
109# --- Resolve tag ---
110#
111# When skipping the tag stage we still need a tag for filenames and
112# ldflags, so fall back to git describe.
113
114set -l tag
115
116if __should_run tag
117 # --- Guards ---
118
119 set -l branch (git symbolic-ref --short HEAD)
120 if test "$branch" != dev
121 echo "Error: not on dev branch (on $branch)" >&2
122 exit 1
123 end
124
125 if test (git status --porcelain=2 | wc -l) -ne 0
126 echo "Error: git working tree is dirty" >&2
127 exit 1
128 end
129
130 # --- Fetch tags ---
131
132 git tag -d nightly 2>/dev/null; or true
133 git fetch upstream --tags
134
135 # --- Compute next fork tag ---
136
137 set -l upstream_ver (git tag -l "v*" | grep -v -- '-fork\.' | sort -V | tail -1; or echo v0.0.0)
138 set -l existing (git tag -l "$upstream_ver-fork.*" | wc -l | string trim)
139 set -l next_num (math $existing + 1)
140 set tag "$upstream_ver-fork.$next_num"
141
142 # --- Confirm creation ---
143
144 read -P "Create fork release $tag? [y/N] " confirm
145 if test "$confirm" != y -a "$confirm" != Y
146 echo Aborted.
147 exit 0
148 end
149
150 # --- Push branch (force because we amended) ---
151
152 git push --force-with-lease; or begin
153 echo "Error: branch push failed" >&2
154 exit 1
155 end
156
157 # --- Tag and push ---
158
159 git tag -a $tag; or begin
160 echo "Error: tagging failed" >&2
161 exit 1
162 end
163
164 git push soft $tag; or begin
165 echo "Error: tag push failed — deleting local tag" >&2
166 git tag -d $tag 2>/dev/null
167 exit 1
168 end
169
170 echo "Released $tag"
171else
172 set tag (git describe --tags --always 2>/dev/null; or echo dev)
173end
174
175# --- Build ---
176
177if __should_run build
178 set -l ldflags "-s -w -X git.secluded.site/crush/internal/version.Version=$tag"
179
180 rm -rf dist
181 mkdir -p dist
182
183 for target in $targets
184 set -l os (echo $target | cut -d/ -f1)
185 set -l arch (echo $target | cut -d/ -f2)
186 set -l ext ""
187 if test "$os" = windows
188 set ext .exe
189 end
190
191 echo "Building $os/$arch..."
192 env CGO_ENABLED=0 GOEXPERIMENT=greenteagc GOOS=$os GOARCH=$arch \
193 go build -o "dist/crush-$tag-$os-$arch$ext" -ldflags "$ldflags"; or begin
194 echo "Error: build failed for $os/$arch" >&2
195 exit 1
196 end
197 end
198end
199
200# --- Pack ---
201
202if __should_run pack
203 for suffix in $pack_targets
204 set -l bin "dist/crush-$tag-$suffix"
205 if test -f "$bin"
206 echo "Packing $suffix..."
207 upx -q "$bin"
208 end
209 end
210end
211
212# --- Upload ---
213
214if __should_run upload
215 fish -c "release upload crush $tag --latest dist/*"
216end