diff --git a/.github/actions/cross-tools/action.yml b/.github/actions/cross-tools/action.yml new file mode 100644 index 0000000..538f4ce --- /dev/null +++ b/.github/actions/cross-tools/action.yml @@ -0,0 +1,16 @@ +name: cross-tools +description: Rust targets, zig, cargo-zigbuild and cross for scripts/build.sh +runs: + using: composite + steps: + - shell: bash + run: | + rustup target add x86_64-unknown-linux-musl aarch64-unknown-linux-musl i686-unknown-linux-musl \ + armv7-unknown-linux-musleabihf riscv64gc-unknown-linux-musl x86_64-pc-windows-gnu \ + aarch64-pc-windows-gnullvm x86_64-unknown-freebsd x86_64-unknown-netbsd + curl -sSLo zig.tar.xz https://ziglang.org/download/0.16.0/zig-x86_64-linux-0.16.0.tar.xz + echo "70e49664a74374b48b51e6f3fdfbf437f6395d42509050588bd49abe52ba3d00 zig.tar.xz" | sha256sum -c + mkdir -p "$HOME/zig" && tar -xJf zig.tar.xz -C "$HOME/zig" --strip-components=1 && rm zig.tar.xz + echo "$HOME/zig" >>"$GITHUB_PATH" + cargo install cargo-zigbuild --version 0.23.4 --locked + cargo install cross --version 0.2.5 --locked diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0428f35..1de7a30 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,29 +4,38 @@ on: pull_request: permissions: contents: read +env: + CARGO_TERM_COLOR: always jobs: + lint: + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v5 + - run: rustup component add rustfmt clippy + - run: cargo fmt --check + - run: cargo clippy --locked --all-targets -- -D warnings + test: strategy: fail-fast: false matrix: - os: [ubuntu-latest, macos-latest, windows-latest] + os: [ubuntu-24.04, ubuntu-24.04-arm, macos-15, macos-15-intel, windows-2025] runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v4 - - uses: actions/setup-go@v5 - with: - go-version-file: go.mod - - name: format - if: runner.os == 'Linux' - run: test -z "$(gofmt -l .)" - - run: go vet ./... - - run: go test ./... - - name: race - if: runner.os != 'Windows' - run: go test -race ./... - - name: cross-build - if: runner.os == 'Linux' + - uses: actions/checkout@v5 + - run: cargo test --release --locked --all-targets + - run: cargo run --release --locked -- --no-ui . + + cross: + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v5 + - uses: ./.github/actions/cross-tools + - name: every target but macOS (built natively by the test job) + run: TARGETS="linux/amd64 linux/arm64 linux/386 linux/arm linux/riscv64 windows/amd64 windows/arm64 freebsd/amd64 netbsd/amd64" scripts/build.sh + - name: static binaries run under qemu run: | - for t in linux/arm64 linux/386 darwin/arm64 windows/amd64 freebsd/amd64; do - GOOS=${t%/*} GOARCH=${t#*/} CGO_ENABLED=0 go build -o /dev/null ./cmd/mapsize + sudo apt-get update -q && sudo apt-get install -yq qemu-user + for t in "arm64 qemu-aarch64" "arm qemu-arm" "riscv64 qemu-riscv64"; do + ${t#* } dist/mapsize-linux-${t%% *} --no-ui src >/dev/null done diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..e1ea2cb --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,46 @@ +# On a version tag: build every target, package, and publish a GitHub +# release whose notes are the tag's CHANGELOG.md section. +name: release +on: + push: + tags: ["v*"] +permissions: + contents: read +env: + CARGO_TERM_COLOR: always +jobs: + build-cross: + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v5 + - uses: ./.github/actions/cross-tools + - run: TARGETS="linux/amd64 linux/arm64 linux/386 linux/arm linux/riscv64 windows/amd64 windows/arm64 freebsd/amd64 netbsd/amd64" scripts/build.sh + - uses: actions/upload-artifact@v4 + with: {name: bin-cross, path: dist/mapsize-*} + + build-macos: + runs-on: macos-15 + steps: + - uses: actions/checkout@v5 + - run: rustup target add x86_64-apple-darwin aarch64-apple-darwin + - run: TARGETS="darwin/amd64 darwin/arm64" scripts/build.sh + - uses: actions/upload-artifact@v4 + with: {name: bin-macos, path: dist/mapsize-*} + + publish: + needs: [build-cross, build-macos] + runs-on: ubuntu-24.04 + permissions: + contents: write + steps: + - uses: actions/checkout@v5 + - uses: actions/download-artifact@v4 + with: {path: dist, merge-multiple: true} + - run: scripts/release.sh "$GITHUB_REF_NAME" dist dist/release + - name: release + env: + GH_TOKEN: ${{ github.token }} + run: | + v=${GITHUB_REF_NAME#v} + awk -v h="## $v" 'index($0, h) == 1 {f=1; next} /^## /{f=0} f' CHANGELOG.md >notes.md + gh release create "$GITHUB_REF_NAME" --title "$GITHUB_REF_NAME" --notes-file notes.md dist/release/* diff --git a/CHANGELOG.md b/CHANGELOG.md index 1762481..3a8a656 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,27 @@ # Changelog +## Unreleased + +### Changed +- Rewritten in Rust. Flags, keys, views, themes, the configuration file and + the snapshot format are unchanged: snapshots from earlier releases open as + before, and older releases open new ones. Measured against 1.2.0 on the + same machine, scans are about 1.3× faster with a third less memory, cold + treemap frames about 3× faster, and filters up to 1.8× faster (see + PERFORMANCE.md). +- Release builds: Linux (x86-64, ARM64, 32-bit x86, ARMv7, RISC-V 64), + macOS (Intel, Apple silicon), Windows (x86-64, ARM64), FreeBSD and NetBSD + (x86-64), built and published by CI. OpenBSD and FreeBSD/ARM64 builds are + no longer produced. +- `--log` and `--log-level` are accepted but no log is written yet; + `--cpuprofile` and `--memprofile` are removed. +- Install from source with `cargo install`; the Homebrew formula builds with + Rust. + +### Fixed +- A search containing certain non-ASCII text (for example `voilà`) no longer + hangs. + ## 1.2.0 — 2026-10-03 ### Changed diff --git a/Makefile b/Makefile index cc51ac7..0bfe7c8 100644 --- a/Makefile +++ b/Makefile @@ -1,80 +1,54 @@ -BIN := bin -PKG := ./... -VERSION ?= $(shell git describe --tags --always --dirty 2>/dev/null || echo dev) -LDFLAGS := -s -w PREFIX ?= /usr/local +VERSION ?= $(shell sed -n 's/^version = "\(.*\)"/\1/p' Cargo.toml | head -1) -.PHONY: build install man brew-formula test race vet fmt fmt-check benchmark fuzz release clean treegen +.PHONY: build install man test lint fmt fmt-check benchmark treegen release brew-formula clean build: - go build -trimpath -ldflags '$(LDFLAGS)' -o $(BIN)/mapsize ./cmd/mapsize - go build -trimpath -ldflags '$(LDFLAGS)' -o $(BIN)/treegen ./cmd/treegen + cargo build --release --locked install: build - install -Dm755 $(BIN)/mapsize $(DESTDIR)$(PREFIX)/bin/mapsize + install -Dm755 target/release/mapsize $(DESTDIR)$(PREFIX)/bin/mapsize install -Dm644 docs/mapsize.1 $(DESTDIR)$(PREFIX)/share/man/man1/mapsize.1 man: man -l docs/mapsize.1 test: - go test $(PKG) + cargo test --release --locked -race: - go test -race $(PKG) - -vet: - go vet $(PKG) +lint: + cargo clippy --locked --all-targets -- -D warnings fmt: - gofmt -w . + cargo fmt fmt-check: - @test -z "$$(gofmt -l .)" || (gofmt -l . && echo 'run make fmt' && exit 1) + cargo fmt --check benchmark: - go test -run=NONE -bench=. -benchmem $(PKG) + cargo run --release --locked --example bench -fuzz: - go test -run=NONE -fuzz=FuzzLoad -fuzztime=30s ./internal/snapshot - go test -run=NONE -fuzz=FuzzSquarify -fuzztime=30s ./internal/treemap - go test -run=NONE -fuzz=FuzzParse -fuzztime=30s ./internal/filter +treegen: + cargo build --release --locked --example treegen -# Cross-built archives plus .deb/.rpm/.apk/Arch packages in $(BIN)/release. -REL := $(BIN)/release -NFPM := go run github.com/goreleaser/nfpm/v2/cmd/nfpm@v2.41.1 +# Binaries for every target this host can build (see scripts/build.sh), then +# archives, Linux packages and SHA256SUMS in dist/release. Tagged releases are +# built on GitHub's runners by .github/workflows/release.yml instead. release: - rm -rf $(REL) && mkdir -p $(REL) - @set -e; v=$(VERSION); v=$${v#v}; \ - for t in linux/amd64 linux/arm64 linux/386 linux/arm darwin/amd64 darwin/arm64 \ - windows/amd64 windows/arm64 freebsd/amd64 freebsd/arm64 openbsd/amd64 netbsd/amd64; do \ - os=$${t%/*}; arch=$${t#*/}; ext=; [ $$os = windows ] && ext=.exe; \ - name=mapsize-$$v-$$os-$$arch; d=$(REL)/$$name; echo "$$os/$$arch"; mkdir -p $$d; \ - CGO_ENABLED=0 GOOS=$$os GOARCH=$$arch GOARM=7 go build -trimpath \ - -ldflags '$(LDFLAGS) -X github.com/andydixon/mapsize/internal/brand.Version='$$v \ - -o $$d/mapsize$$ext ./cmd/mapsize; \ - cp README.md CHANGELOG.md LICENSE docs/mapsize.1 $$d/; \ - if [ $$os = windows ]; then (cd $(REL) && zip -qr $$name.zip $$name); \ - else tar -C $(REL) -czf $(REL)/$$name.tar.gz $$name; fi; \ - if [ $$os = linux ]; then mkdir -p $(REL)/pkg && cp $$d/mapsize $(REL)/pkg/; \ - a=$$arch; [ $$a = arm ] && a=arm7; \ - for f in deb rpm apk archlinux; do \ - VERSION=$$v ARCH=$$a $(NFPM) pkg -f nfpm.yaml -p $$f -t $(REL)/; done; rm -rf $(REL)/pkg; fi; \ - rm -rf $$d; \ - done; \ - cd $(REL) && sha256sum * > SHA256SUMS + scripts/build.sh || true + scripts/release.sh $(VERSION) dist dist/release # Homebrew formula for the tagged release. The tag must already be pushed: # the checksum is of GitHub's source tarball for that tag. BREW_REPO := https://github.com/andydixon/mapsize brew-formula: @set -e; v=$(VERSION); v=$${v#v}; \ - case $$v in *-*) echo "brew-formula: HEAD is not exactly a release tag ($$v)"; exit 1;; esac; \ url=$(BREW_REPO)/archive/refs/tags/v$$v.tar.gz; \ sum=$$(curl -fsSL "$$url" | sha256sum | cut -d' ' -f1) || { echo "cannot fetch $$url (tag pushed?)"; exit 1; }; \ - mkdir -p $(BIN)/homebrew; \ - sed -e '/^#/d' -e "s/@VERSION@/$$v/g" -e "s/@SHA256@/$$sum/" packaging/homebrew/mapsize.rb.in > $(BIN)/homebrew/mapsize.rb; \ - echo "wrote $(BIN)/homebrew/mapsize.rb (v$$v, sha256 $$sum)" + mkdir -p dist/homebrew; \ + sed -e '/^#/d' -e "s/@VERSION@/$$v/g" -e "s/@SHA256@/$$sum/" packaging/homebrew/mapsize.rb.in > dist/homebrew/mapsize.rb; \ + echo "wrote dist/homebrew/mapsize.rb (v$$v, sha256 $$sum)" clean: - rm -rf $(BIN) + cargo clean + rm -rf dist diff --git a/README.md b/README.md index 4d397b6..0fd7e3b 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,18 @@ # mapsize An interactive, full-terminal disk usage analyser built around a live, -navigable treemap. Think WinDirStat + ncdu + WizTree, in one Go binary that -runs over SSH. +navigable treemap. Think WinDirStat + ncdu + WizTree, in one Rust binary +that runs over SSH. -> `mapsize` is a working name; branding lives in `internal/brand`. +> `mapsize` is a working name; branding lives in `src/brand.rs`. ## Status -Version 1.0. See [CHANGELOG.md](CHANGELOG.md), [ROADMAP.md](ROADMAP.md) for +Version 1.2. See [CHANGELOG.md](CHANGELOG.md), [ROADMAP.md](ROADMAP.md) for what comes next, and [PERFORMANCE.md](PERFORMANCE.md) for measured numbers. -Tested on Linux; macOS and Windows builds are produced but trash and -file-manager integration have not yet been tested there. +Releases cover Linux, macOS, Windows, FreeBSD and NetBSD. It is tested on +Linux; CI also runs the test suite on macOS and Windows, but trash and +file-manager integration have not yet been tried by hand there. ## What it does @@ -70,9 +71,9 @@ the map or scrolls lists. Everything works without a mouse (`--no-mouse`). ## Install ```sh -go install github.com/andydixon/mapsize/cmd/mapsize@v1.0.0 -# or -make build # → bin/mapsize +cargo install --locked --git https://github.com/andydixon/mapsize +# or, from a checkout +make build # → target/release/mapsize sudo make install # binary + man page under /usr/local (PREFIX=… to change) # Homebrew (macOS and Linux) diff --git a/ROADMAP.md b/ROADMAP.md index 796637c..c30475f 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -3,14 +3,14 @@ Status markers: `[x]` done, `[~]` partial, `[ ]` not started. ## 0.1 — inventory engine -- [x] Go project skeleton, Makefile, CI +- [x] project skeleton, Makefile, CI - [x] portable filesystem scanner, bounded worker pool, no-deadlock scheduler - [x] inventory node model, incremental aggregation - [x] cancellation, progress counters, error accounting - [x] CLI: `--top`, `--json`, `--csv`, `--no-ui` ## 0.2 — TUI shell -- [x] full-screen Bubble Tea v2 app, canvas renderer, frame cache +- [x] full-screen terminal app, canvas renderer, frame cache - [x] responsive layout (large / medium / small / too-small) - [x] resize handling, status bar, progressive display, directory list diff --git a/docs/mapsize.1 b/docs/mapsize.1 index 723d283..15c6271 100644 --- a/docs/mapsize.1 +++ b/docs/mapsize.1 @@ -142,18 +142,10 @@ colouring, or prints the most significant changes with \fB\-\-no\-ui\fR (\fB\-\-top\fR sets how many; default 30). .SS Diagnostics .TP -.BI \-\-log " FILE" -Append a log to \fIFILE\fR without disturbing the interface. Full paths are -logged only at \fBdebug\fR level and above. -.TP -.BI \-\-log\-level " LEVEL" -.BR error ", " warn ", " info " (default), " debug " or " trace . -.TP -.BI \-\-cpuprofile " FILE" -Write a Go CPU profile. -.TP -.BI \-\-memprofile " FILE" -Write a Go heap profile on exit. +.BI \-\-log " FILE" "\fR, \fP\-\-log\-level " LEVEL +Accepted for compatibility with earlier releases; currently no log is +written. \fILEVEL\fR must still be one of +.BR error ", " warn ", " info ", " debug " or " trace . .TP .B \-\-version Print the version and exit. diff --git a/nfpm.yaml b/nfpm.yaml deleted file mode 100644 index 20e9dbd..0000000 --- a/nfpm.yaml +++ /dev/null @@ -1,14 +0,0 @@ -name: mapsize -arch: ${ARCH} -version: ${VERSION} -maintainer: Andy Dixon -description: Interactive full-terminal disk usage analyser with a live treemap. -homepage: https://github.com/andydixon/mapsize -license: GPL-3.0-or-later -contents: - - src: bin/release/pkg/mapsize - dst: /usr/bin/mapsize - - src: docs/mapsize.1 - dst: /usr/share/man/man1/mapsize.1 - file_info: - mode: 0644 diff --git a/packaging/homebrew/mapsize.rb.in b/packaging/homebrew/mapsize.rb.in index 2849f70..ee3a9b4 100644 --- a/packaging/homebrew/mapsize.rb.in +++ b/packaging/homebrew/mapsize.rb.in @@ -1,6 +1,6 @@ # Homebrew formula template for mapsize. `make brew-formula` fills in the # version and the checksum of the GitHub source tarball and writes the -# result to bin/homebrew/mapsize.rb; copy that into your tap +# result to dist/homebrew/mapsize.rb; copy that into your tap # (github.com/andydixon/homebrew-tap, Formula/mapsize.rb). class Mapsize < Formula desc "Interactive terminal disk usage analyser with a navigable treemap" @@ -10,11 +10,10 @@ class Mapsize < Formula license "GPL-3.0-or-later" head "https://github.com/andydixon/mapsize.git", branch: "master" - depends_on "go" => :build + depends_on "rust" => :build def install - ldflags = "-X github.com/andydixon/mapsize/internal/brand.Version=#{version}" - system "go", "build", *std_go_args(ldflags:), "./cmd/mapsize" + system "cargo", "install", *std_cargo_args man1.install "docs/mapsize.1" end diff --git a/packaging/repo.sh b/packaging/repo.sh index 76f914a..7d2919b 100755 --- a/packaging/repo.sh +++ b/packaging/repo.sh @@ -10,10 +10,11 @@ # Every index is rebuilt over all the packages in the repository, so other # projects publishing there with their copy of this script are kept. # -# Needs: go, docker, gpg, apt-ftparchive (apt-utils), flock. +# Needs: cargo with cargo-zigbuild and zig (static musl cross-builds), curl +# (to fetch the pinned nfpm), docker, gpg, apt-ftparchive (apt-utils), flock. # REPO_DIR where the repository lives (default below) # REPO_GNUPGHOME GnuPG home holding the signing key (default below) -# ARCHS Go architectures to package (default below) +# ARCHS package architectures (default below) # # The same script, with its own project block, is in ../vault/packaging. set -euo pipefail @@ -27,19 +28,30 @@ HOMEPAGE=https://github.com/andydixon/mapsize LICENSE=GPL-3.0-or-later MANPAGES=docs/*.1 DOCS="README.md CHANGELOG.md LICENSE" -# build OUT: build the binary to OUT, in the source tree, with GOOS, GOARCH -# and GOARM set and VERSION the release. +# Package architecture -> Rust target (static musl binaries). +triple() { + case $1 in + amd64) echo x86_64-unknown-linux-musl ;; + arm64) echo aarch64-unknown-linux-musl ;; + arm) echo armv7-unknown-linux-musleabihf ;; + 386) echo i686-unknown-linux-musl ;; + riscv64) echo riscv64gc-unknown-linux-musl ;; + *) return 1 ;; + esac +} +# build ARCH OUT: build the binary for ARCH to OUT, in the source tree. build() { - CGO_ENABLED=0 go build -trimpath \ - -ldflags "-s -w -X github.com/andydixon/mapsize/internal/brand.Version=$VERSION" \ - -o "$1" ./cmd/mapsize + local t + t=$(triple "$1") || die "no Rust target for $1" + RUSTFLAGS="--remap-path-prefix=$PWD=$NAME" cargo zigbuild --quiet --release --locked --target "$t" + install -D -m 0755 "target/$t/release/$NAME" "$2" } # ---------------------------------------------------------------------------- REPO_DIR=${REPO_DIR:-/home/andy/domains/repo.dixon.cx/public_html} REPO_GNUPGHOME=${REPO_GNUPGHOME:-$HOME/.config/repo.dixon.cx/gnupg} ARCHS=${ARCHS:-"amd64 arm64 arm 386 riscv64"} -NFPM="go run github.com/goreleaser/nfpm/v2/cmd/nfpm@v2.41.1" +NFPM="$(cd "$(dirname "$0")/.." && pwd)/scripts/nfpm.sh" SUITE=stable COMPONENT=main ARCH_REPO=dixon # the pacman repository name: [dixon] @@ -47,7 +59,7 @@ ARCH_REPO=dixon # the pacman repository name: [dixon] say() { echo "repo: $*" >&2; } die() { say "$*"; exit 1; } -for tool in go docker gpg apt-ftparchive flock; do +for tool in cargo cargo-zigbuild zig curl docker gpg apt-ftparchive flock; do command -v $tool >/dev/null || die "needs $tool" done [[ -d $REPO_DIR ]] || die "$REPO_DIR does not exist" @@ -70,9 +82,9 @@ gpg --batch --armor --export-secret-keys "$KEY" >"$WORK/signing.asc" say "building $PACKAGE $VERSION for $ARCHS" mkdir -p "$WORK/out" for arch in $ARCHS; do - goarm= nfarch=$arch - [[ $arch == arm ]] && goarm=7 nfarch=arm7 - (cd "$WORK/src" && GOOS=linux GOARCH=$arch GOARM=$goarm build "$WORK/bin-$arch/$NAME") + nfarch=$arch + [[ $arch == arm ]] && nfarch=arm7 + (cd "$WORK/src" && build "$arch" "$WORK/bin-$arch/$NAME") { cat <<-EOF name: $PACKAGE diff --git a/scripts/build.sh b/scripts/build.sh new file mode 100755 index 0000000..9b628b6 --- /dev/null +++ b/scripts/build.sh @@ -0,0 +1,77 @@ +#!/usr/bin/env bash +# Build mapsize release binaries for the supported operating systems and +# CPU architectures. +# +# scripts/build.sh # every target this host can build, into dist/ +# TARGETS="linux/amd64 darwin/arm64" scripts/build.sh +# OUT=/tmp/bins scripts/build.sh +# +# Binaries are named mapsize-OS-ARCH[.exe]: stripped release builds from the +# locked dependencies (Cargo.lock), with build paths remapped so the output +# does not depend on where the tree lives. Linux binaries are static (musl). +# +# How each target is built, and what the host needs: +# zig cargo-zigbuild and zig on PATH (Linux, Windows) +# mac a Mac, or cargo-zigbuild and zig (macOS) +# cross `cross` and Docker (FreeBSD, NetBSD) +# A requested target the host cannot build is reported, the others are still +# built, and the script exits non-zero. The release workflow +# (.github/workflows/release.yml) builds every target on GitHub's runners. +set -euo pipefail +cd "$(dirname "$0")/.." +root=$PWD + +table() { + # os/arch Rust target method + cat <<-'EOF2' + linux/amd64 x86_64-unknown-linux-musl zig + linux/arm64 aarch64-unknown-linux-musl zig + linux/386 i686-unknown-linux-musl zig + linux/arm armv7-unknown-linux-musleabihf zig + linux/riscv64 riscv64gc-unknown-linux-musl zig + windows/amd64 x86_64-pc-windows-gnu zig + windows/arm64 aarch64-pc-windows-gnullvm zig + darwin/amd64 x86_64-apple-darwin mac + darwin/arm64 aarch64-apple-darwin mac + freebsd/amd64 x86_64-unknown-freebsd cross + netbsd/amd64 x86_64-unknown-netbsd cross + EOF2 +} + +out=${OUT:-dist} +targets=${TARGETS:-$(table | awk '{print $1}')} +mkdir -p -- "$out" +out=$(cd -- "$out" && pwd -P) +case $out in +/ | "$(cd ~ && pwd -P)" | "$root") echo "refusing OUT=$out: use a dedicated directory" >&2; exit 2 ;; +esac +export RUSTFLAGS="${RUSTFLAGS:-} --remap-path-prefix=$root=mapsize --remap-path-prefix=${CARGO_HOME:-$HOME/.cargo}=cargo" +have() { command -v "$1" >/dev/null; } + +status=0 +for want in $targets; do + row=$(table | awk -v t="$want" '$1 == t') + [[ -n $row ]] || { echo "UNKNOWN $want" >&2; status=1; continue; } + read -r _ triple method <<<"$row" + ext=; [[ $want == windows/* ]] && ext=.exe + name=mapsize-${want/\//-}$ext + case $method in + zig) have cargo-zigbuild && have zig && cmd=(cargo zigbuild) || + { echo "SKIPPED $want: needs cargo-zigbuild and zig" >&2; status=1; continue; } ;; + mac) + if [[ $(uname -s) == Darwin ]]; then cmd=(cargo build) + elif have cargo-zigbuild && have zig; then cmd=(cargo zigbuild) + else echo "SKIPPED $want: needs a Mac, or cargo-zigbuild and zig" >&2; status=1; continue; fi ;; + cross) have cross && have docker && cmd=(cross build) || + { echo "SKIPPED $want: needs cross and docker" >&2; status=1; continue; } ;; + esac + if ! "${cmd[@]}" --quiet --release --locked --target "$triple"; then + echo "FAILED $want ($triple)" >&2; status=1; continue + fi + cp -- "target/$triple/release/mapsize$ext" "$out/$name" + if [[ $triple == *-musl* ]] && have file; then + file -L "$out/$name" | grep -q 'static' || { echo "FAILED $want: not statically linked" >&2; status=1; continue; } + fi + echo "built $name ($triple)" +done +exit $status diff --git a/scripts/nfpm.sh b/scripts/nfpm.sh new file mode 100755 index 0000000..e822b90 --- /dev/null +++ b/scripts/nfpm.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash +# Run the pinned nfpm packager, downloading and checksumming it on first use +# (into target/tools). Linux x86-64 and ARM64 hosts only. +set -euo pipefail +version=2.41.1 +case $(uname -m) in +x86_64) arch=x86_64 sum=b3cf95aa6dabed836d09ad7f0c190a13c74c5b1304db60846f0f702ee407f430 ;; +aarch64) arch=arm64 sum=17350a838c8e2c422c6e573ed379b18424565d2de8a2b1cb1b20211976124eb5 ;; +*) echo "nfpm.sh: unsupported host $(uname -m)" >&2; exit 1 ;; +esac +dir=$(cd "$(dirname "$0")/.." && pwd)/target/tools/nfpm-$version +if [[ ! -x $dir/nfpm ]]; then + mkdir -p "$dir" + tgz=$dir/nfpm.tar.gz + curl -fsSL -o "$tgz" "https://github.com/goreleaser/nfpm/releases/download/v$version/nfpm_${version}_Linux_$arch.tar.gz" + echo "$sum $tgz" | sha256sum -c --quiet + tar -xzf "$tgz" -C "$dir" nfpm + rm -f "$tgz" +fi +exec "$dir/nfpm" "$@" diff --git a/scripts/release.sh b/scripts/release.sh new file mode 100755 index 0000000..ceda299 --- /dev/null +++ b/scripts/release.sh @@ -0,0 +1,63 @@ +#!/usr/bin/env bash +# Turn the binaries from scripts/build.sh into release assets: one archive +# per binary (with README, CHANGELOG, LICENSE and the manual page), .deb, +# .rpm, .apk and Arch packages for the Linux binaries, and SHA256SUMS. +# +# scripts/release.sh VERSION [BIN_DIR [OUT_DIR]] # defaults: dist, dist/release +# +# VERSION (e.g. 1.3.0 or v1.3.0) must match Cargo.toml, which is what the +# binaries report. +set -euo pipefail +cd "$(dirname "$0")/.." +root=$PWD +version=${1:?usage: scripts/release.sh VERSION [BIN_DIR [OUT_DIR]]} +version=${version#v} +bins=$(cd "${2:-dist}" && pwd) +out=${3:-dist/release} +cargo_version=$(sed -n 's/^version = "\(.*\)"/\1/p' Cargo.toml | head -1) +[[ $version == "$cargo_version" ]] || { echo "release: VERSION $version does not match Cargo.toml ($cargo_version)" >&2; exit 1; } +rm -rf -- "$out" && mkdir -p -- "$out" +out=$(cd "$out" && pwd) +work=$(mktemp -d) +trap 'rm -rf -- "$work"' EXIT + +shopt -s nullglob +for bin in "$bins"/mapsize-*; do + b=$(basename "$bin") + osarch=${b#mapsize-} + osarch=${osarch%.exe} + os=${osarch%-*} arch=${osarch#*-} + ext=; [[ $os == windows ]] && ext=.exe + name=mapsize-$version-$os-$arch + mkdir -p "$work/$name" + install -m 0755 "$bin" "$work/$name/mapsize$ext" + cp README.md CHANGELOG.md LICENSE docs/mapsize.1 "$work/$name/" + if [[ $os == windows ]]; then (cd "$work" && zip -qr "$out/$name.zip" "$name") + else tar -C "$work" -czf "$out/$name.tar.gz" "$name"; fi + if [[ $os == linux ]]; then + nfarch=$arch; [[ $arch == arm ]] && nfarch=arm7 + cat >"$work/nfpm-$arch.yaml" <<-EOF2 + name: mapsize + arch: $nfarch + platform: linux + version: $version + release: 1 + maintainer: Andy Dixon + description: Interactive full-terminal disk usage analyser with a live treemap. + homepage: https://github.com/andydixon/mapsize + license: GPL-3.0-or-later + contents: + - src: $work/$name/mapsize + dst: /usr/bin/mapsize + file_info: {mode: 0755} + - src: $root/docs/mapsize.1 + dst: /usr/share/man/man1/mapsize.1 + file_info: {mode: 0644} + EOF2 + for fmt in deb rpm apk archlinux; do + scripts/nfpm.sh pkg -f "$work/nfpm-$arch.yaml" -p $fmt -t "$out/" >/dev/null + done + fi +done +(cd "$out" && sha256sum -- * >SHA256SUMS) +echo "$(($(ls "$out" | wc -l) - 1)) assets and SHA256SUMS in $out"