icns

Easily create .icns files (Mac Icons) with this Go library or the included CLI.
Log | Files | Refs | LICENSE

commit 9870ba9c4ab2b3324227fea545421c1b71351c77
parent c1aa75c9b82939d71eee82fc9ccb83cc20752147
Author: Jack Mordaunt <jackmordaunt.dev@gmail.com>
Date:   Fri, 18 Sep 2026 16:09:29 -0400

build: test, fuzz and scan every module in CI

The only workflow published to winget on release, so nothing verified a
commit. Three modules, two of them platform bound, cannot all be built on one
machine, and the decoder parses untrusted files, which is worth fuzzing on
every change rather than by hand.

Diffstat:
A.github/workflows/ci.yml | 135+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mreadme.md | 5+++++
2 files changed, 140 insertions(+), 0 deletions(-)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml @@ -0,0 +1,135 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + schedule: + # Weekly, so new advisories surface without waiting for a commit. + - cron: "0 6 * * 1" + +permissions: + contents: read + +jobs: + library: + name: library (${{ matrix.os }}) + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + steps: + - uses: actions/checkout@v4 + # go-version-file reads the go directive, so CI proves the declared + # minimum actually builds rather than whatever is newest. + - uses: actions/setup-go@v5 + with: + go-version-file: go.mod + cache-dependency-path: | + go.sum + cmd/preview/go.sum + cmd/shell-extension/go.sum + - run: go build ./... + - run: go test -count=1 ./... + + race: + name: library under the race detector + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version-file: go.mod + cache-dependency-path: go.sum + # The encoder resizes every icon concurrently. + - run: go test -count=1 -race ./... + + fuzz: + name: decoder fuzzing + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version-file: go.mod + cache-dependency-path: go.sum + - run: go test -run='^$' -fuzz=FuzzDecode -fuzztime=120s . + - name: Keep any crasher for download + if: failure() + uses: actions/upload-artifact@v4 + with: + name: fuzz-corpus + path: testdata/fuzz + + shell-extension: + name: shell extension + runs-on: windows-latest + defaults: + run: + working-directory: cmd/shell-extension + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version-file: cmd/shell-extension/go.mod + cache-dependency-path: cmd/shell-extension/go.sum + # The DLL is built with -buildmode=c-shared, which needs cgo. + - run: gcc --version + - run: go test -count=1 ./... + # Outside the workspace the module resolves the published library, which + # is what `go install` gets; it catches a go.sum that never learned it. + - run: go build ./... + env: + GOWORK: "off" + + preview: + name: preview (${{ matrix.os }}) + # Gio needs a long list of X11 and Wayland headers on Linux, so the GUI is + # only built where its dependencies ship with the runner. + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [macos-latest, windows-latest] + defaults: + run: + working-directory: cmd/preview + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version-file: cmd/preview/go.mod + cache-dependency-path: cmd/preview/go.sum + - run: go build ./... + - run: go build ./... + env: + GOWORK: "off" + + checks: + name: formatting, vet and vulnerabilities + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version: stable + cache-dependency-path: | + go.sum + cmd/preview/go.sum + cmd/shell-extension/go.sum + - name: gofmt + run: | + unformatted="$(gofmt -l .)" + if [ -n "$unformatted" ]; then + echo "not gofmt clean:" + echo "$unformatted" + exit 1 + fi + # ./... covers only the module holding the working directory, even in a + # workspace, so each module is named. + - run: go vet ./... ./cmd/preview/... ./cmd/shell-extension/... + - name: govulncheck + run: | + go install golang.org/x/vuln/cmd/govulncheck@latest + govulncheck ./... diff --git a/readme.md b/readme.md @@ -1,5 +1,8 @@ # icns +[![CI](https://github.com/JackMordaunt/icns/actions/workflows/ci.yml/badge.svg)](https://github.com/JackMordaunt/icns/actions/workflows/ci.yml) +[![Go Reference](https://pkg.go.dev/badge/github.com/jackmordaunt/icns/v4.svg)](https://pkg.go.dev/github.com/jackmordaunt/icns/v4) + Easily convert `.jpg` and `.png` to `.icns` with the command line tool `icnsify`, or use the library to convert from any `image.Image` to `.icns`. `go get github.com/jackmordaunt/icns/v4` @@ -139,6 +142,8 @@ go test ./... ./cmd/preview/... ./cmd/shell-extension/... The two command modules require the library at a published tag. Inside the workspace that requirement only shapes the module graph; the code always comes from the working tree, so the tag can lag behind without affecting development. +CI builds and tests the library on Linux, macOS and Windows, runs the encoder under the race detector, fuzzes the decoder, and reports `gofmt`, `go vet` and `govulncheck`. The shell extension is tested on Windows, where cgo can reach a C compiler, and `preview` is built on macOS and Windows, since Gio needs a long list of X11 and Wayland headers on Linux. Both command modules are also built with `GOWORK=off`, which is what `go install` sees. + Releasing: tag the root module (`vX.Y.Z`), which releases the library and `icnsify` together. When `go install .../cmd/preview@latest`, or a shell extension built outside the checkout, should pick up a newer library, bump that module's requirement and tidy it outside the workspace: ```powershell