release-fork.fish

  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