neiam
/icon-studio
public · Issues · Pulls · Labels · Forks · Compare · Actions failure · Badges · Packages
Log in to mark this repository.
Publish .deb and .rpm packages to the forge
af1c436 · gmorell · 2026-09-22 03:54
Message
{commit_body(@commit)}
Files changed
added
.forgejo/workflows/packages.yml
+233
−0
| 1 | +# Builds Icon Studio and publishes a .deb and an .rpm to this forge's | |
| 2 | +# package registries, the way the other neiam Rust projects do. | |
| 3 | +# | |
| 4 | +# x86_64 only: that is what the desktops this runs on are, and a second | |
| 5 | +# architecture is a second runner to keep alive for no one. | |
| 6 | +# | |
| 7 | +# Nothing to configure. `${{ github.token }}` is minted for the job and | |
| 8 | +# authorised to publish to THIS repository's packages and nowhere else, | |
| 9 | +# so there is no secret to create first. | |
| 10 | +name: packages | |
| 11 | + | |
| 12 | +on: | |
| 13 | + push: | |
| 14 | + branches: [master] | |
| 15 | + tags: ['v*'] | |
| 16 | + pull_request: | |
| 17 | + branches: [master] | |
| 18 | + | |
| 19 | +env: | |
| 20 | + CARGO_TERM_COLOR: always | |
| 21 | + | |
| 22 | +jobs: | |
| 23 | + package: | |
| 24 | + runs-on: self-hosted | |
| 25 | + steps: | |
| 26 | + - uses: actions/checkout@v4 | |
| 27 | + | |
| 28 | + - name: Install system dependencies | |
| 29 | + run: | | |
| 30 | + set -euo pipefail | |
| 31 | + | |
| 32 | + # A hosted runner is an unprivileged user with sudo; a job | |
| 33 | + # container is already root and often ships no sudo at all, so | |
| 34 | + # hardcoding either one fails before the first package is | |
| 35 | + # fetched. Ask what we are. | |
| 36 | + if [ "$(id -u)" -eq 0 ]; then | |
| 37 | + SUDO= | |
| 38 | + elif command -v sudo >/dev/null 2>&1; then | |
| 39 | + SUDO=sudo | |
| 40 | + else | |
| 41 | + echo "Not root and no sudo; cannot install system dependencies." >&2 | |
| 42 | + exit 1 | |
| 43 | + fi | |
| 44 | + | |
| 45 | + # eframe/winit need the windowing and GL headers; rfd is built | |
| 46 | + # against the XDG portal rather than GTK, so there is no GTK | |
| 47 | + # stack here. dpkg-dev matters more than it looks: without it | |
| 48 | + # cargo-deb cannot run dpkg-shlibdeps, and the .deb ships with | |
| 49 | + # an empty Depends that installs anywhere and then fails to | |
| 50 | + # start on a machine missing libxkbcommon. | |
| 51 | + if command -v apt-get >/dev/null 2>&1; then | |
| 52 | + $SUDO apt-get update | |
| 53 | + $SUDO apt-get install -y --no-install-recommends \ | |
| 54 | + build-essential pkg-config dpkg-dev rpm \ | |
| 55 | + libxkbcommon-dev libwayland-dev \ | |
| 56 | + libx11-dev libxcursor-dev libxrandr-dev libxi-dev \ | |
| 57 | + libgl1-mesa-dev | |
| 58 | + elif command -v dnf >/dev/null 2>&1; then | |
| 59 | + $SUDO dnf install -y gcc pkgconf-pkg-config rpm-build dpkg-dev \ | |
| 60 | + libxkbcommon-devel wayland-devel \ | |
| 61 | + libX11-devel libXcursor-devel libXrandr-devel libXi-devel \ | |
| 62 | + mesa-libGL-devel | |
| 63 | + else | |
| 64 | + echo "Unsupported package manager." >&2 | |
| 65 | + exit 1 | |
| 66 | + fi | |
| 67 | + | |
| 68 | + - name: Install Rust | |
| 69 | + uses: dtolnay/rust-toolchain@stable | |
| 70 | + with: | |
| 71 | + components: rustfmt, clippy | |
| 72 | + | |
| 73 | + - name: Setup Rust cache | |
| 74 | + uses: Swatinem/rust-cache@v2 | |
| 75 | + | |
| 76 | + - name: Check formatting | |
| 77 | + run: cargo fmt -- --check | |
| 78 | + | |
| 79 | + - name: Run clippy | |
| 80 | + run: cargo clippy --all-targets -- -D warnings | |
| 81 | + | |
| 82 | + # Includes the comparison against the icons the original studio | |
| 83 | + # shipped, so a rendering regression stops the release rather than | |
| 84 | + # being packaged. | |
| 85 | + - name: Run tests | |
| 86 | + run: cargo test --verbose | |
| 87 | + | |
| 88 | + - name: Build release binary | |
| 89 | + run: cargo build --release --verbose | |
| 90 | + | |
| 91 | + - name: Strip binary | |
| 92 | + run: strip target/release/icon-studio | |
| 93 | + | |
| 94 | + # The icons and desktop entry are committed, but regenerating them | |
| 95 | + # proves presets/icon-studio.toml still renders — the packaged icon | |
| 96 | + # and the document that describes it cannot drift apart. | |
| 97 | + - name: Regenerate the application icon | |
| 98 | + run: | | |
| 99 | + set -euo pipefail | |
| 100 | + cargo run --release --example make_app_icon | |
| 101 | + git --no-pager diff --stat -- assets/icons || true | |
| 102 | + | |
| 103 | + # Built with stable, not pinned: cargo-deb has repeatedly needed a | |
| 104 | + # newer rustc than its own declared MSRV admits. These are | |
| 105 | + # build-time utilities that never link into the binary, so holding | |
| 106 | + # them to this crate's toolchain buys nothing and breaks whenever | |
| 107 | + # the ecosystem moves. `--locked` keeps two runs a month apart | |
| 108 | + # installing the same bytes. | |
| 109 | + - name: Install the packaging tools | |
| 110 | + run: | | |
| 111 | + set -euo pipefail | |
| 112 | + cargo install cargo-deb --locked | |
| 113 | + cargo install cargo-generate-rpm --locked | |
| 114 | + | |
| 115 | + # Every CI build has to carry a distinct version, or the second one | |
| 116 | + # publishes the same filename with different bytes and the registry | |
| 117 | + # correctly refuses it with a 409 — and nothing that installed the | |
| 118 | + # first ever sees the second. | |
| 119 | + # | |
| 120 | + # `+` rather than `-`: RPM forbids `-` in a version outright, and in | |
| 121 | + # both ecosystems 0.1.0+dev.<stamp> sorts *after* 0.1.0, which is | |
| 122 | + # what you want from a repository you added to run the latest build. | |
| 123 | + # A tag publishes the bare Cargo version. | |
| 124 | + # | |
| 125 | + # One stamp for the whole job, so the .deb and .rpm from a single | |
| 126 | + # run agree about what they are. | |
| 127 | + - name: Stamp the package version | |
| 128 | + id: version | |
| 129 | + run: | | |
| 130 | + set -euo pipefail | |
| 131 | + base=$(grep -m1 '^version = ' Cargo.toml | cut -d'"' -f2) | |
| 132 | + | |
| 133 | + if [ "${GITHUB_REF_TYPE:-}" = "tag" ]; then | |
| 134 | + version="$base" | |
| 135 | + else | |
| 136 | + version="$base+dev.$(date -u +%s)" | |
| 137 | + fi | |
| 138 | + | |
| 139 | + echo "version: $version" | |
| 140 | + echo "version=$version" >> "$GITHUB_OUTPUT" | |
| 141 | + | |
| 142 | + # Rewritten after the release build, so the binary is cached | |
| 143 | + # against the unstamped manifest and only packaging sees this. | |
| 144 | + sed -i "0,/^version = .*/s//version = \"$version\"/" Cargo.toml | |
| 145 | + | |
| 146 | + # The build cache can restore a target/ that still holds packages | |
| 147 | + # from an earlier run. The publish step globs these directories, so | |
| 148 | + # a stale .deb would be re-uploaded under its old version alongside | |
| 149 | + # the new one. | |
| 150 | + - name: Clear previous package output | |
| 151 | + run: rm -rf target/debian target/generate-rpm | |
| 152 | + | |
| 153 | + - name: Build the Debian package | |
| 154 | + run: cargo deb --no-build --no-strip | |
| 155 | + | |
| 156 | + - name: Build the RPM package | |
| 157 | + run: cargo generate-rpm | |
| 158 | + | |
| 159 | + - name: Show what was built | |
| 160 | + run: ls -l target/debian/*.deb target/generate-rpm/*.rpm | |
| 161 | + | |
| 162 | + # `vars.CI_FORGE_URL` is a forge built-in, but an older forge build | |
| 163 | + # will not inject it; fall back to what the runner already knows. | |
| 164 | + - name: Resolve the forge URL | |
| 165 | + env: | |
| 166 | + CI_FORGE_URL: ${{ vars.CI_FORGE_URL }} | |
| 167 | + run: | | |
| 168 | + set -euo pipefail | |
| 169 | + forge_url=${CI_FORGE_URL:-${GITHUB_SERVER_URL:-}} | |
| 170 | + | |
| 171 | + if [ -z "$forge_url" ]; then | |
| 172 | + echo "No forge URL available; not publishing." | |
| 173 | + echo "PUBLISH=false" >> "$GITHUB_ENV" | |
| 174 | + exit 0 | |
| 175 | + fi | |
| 176 | + | |
| 177 | + echo "publishing to $forge_url" | |
| 178 | + echo "FORGE_URL=$forge_url" >> "$GITHUB_ENV" | |
| 179 | + echo "PUBLISH=true" >> "$GITHUB_ENV" | |
| 180 | + | |
| 181 | + # Pull requests do not publish: a PR build is a check, and a | |
| 182 | + # registry carrying every proposed change is not one anyone can | |
| 183 | + # install from. | |
| 184 | + - name: Publish the .deb and the .rpm | |
| 185 | + if: github.event_name != 'pull_request' && env.PUBLISH == 'true' | |
| 186 | + env: | |
| 187 | + REPOSITORY: ${{ github.repository }} | |
| 188 | + TOKEN: ${{ github.token }} | |
| 189 | + run: | | |
| 190 | + set -euo pipefail | |
| 191 | + | |
| 192 | + for deb in target/debian/*.deb; do | |
| 193 | + echo "publishing $deb" | |
| 194 | + curl --fail-with-body -sS \ | |
| 195 | + -u "ci:$TOKEN" \ | |
| 196 | + -H "Content-Type: application/vnd.debian.binary-package" \ | |
| 197 | + --data-binary "@$deb" \ | |
| 198 | + "$FORGE_URL/pkg/deb/$REPOSITORY/upload?dist=stable&component=main" | |
| 199 | + echo | |
| 200 | + done | |
| 201 | + | |
| 202 | + for rpm in target/generate-rpm/*.rpm; do | |
| 203 | + echo "publishing $rpm" | |
| 204 | + curl --fail-with-body -sS \ | |
| 205 | + -u "ci:$TOKEN" \ | |
| 206 | + -H "Content-Type: application/x-rpm" \ | |
| 207 | + --data-binary "@$rpm" \ | |
| 208 | + "$FORGE_URL/pkg/rpm/$REPOSITORY/upload" | |
| 209 | + echo | |
| 210 | + done | |
| 211 | + | |
| 212 | + - name: How to install it | |
| 213 | + if: github.event_name != 'pull_request' && env.PUBLISH == 'true' | |
| 214 | + env: | |
| 215 | + OWNER: ${{ github.repository_owner }} | |
| 216 | + run: | | |
| 217 | + cat <<EOF | |
| 218 | + # Debian / Ubuntu | |
| 219 | + curl -fsSL $FORGE_URL/pkg/_keys/deb/$OWNER | sudo tee /etc/apt/keyrings/$OWNER.asc >/dev/null | |
| 220 | + echo "deb [signed-by=/etc/apt/keyrings/$OWNER.asc] $FORGE_URL/pkg/deb/$OWNER/_all stable main" | sudo tee /etc/apt/sources.list.d/$OWNER.list | |
| 221 | + sudo apt update && sudo apt install neiam-icon-studio | |
| 222 | + | |
| 223 | + # Fedora / RHEL / openSUSE — /etc/yum.repos.d/$OWNER.repo | |
| 224 | + [$OWNER] | |
| 225 | + name=$OWNER | |
| 226 | + baseurl=$FORGE_URL/pkg/rpm/$OWNER/_all | |
| 227 | + enabled=1 | |
| 228 | + repo_gpgcheck=1 | |
| 229 | + # cargo-generate-rpm produces unsigned packages; the forge signs | |
| 230 | + # the index, not what it was handed. | |
| 231 | + gpgcheck=0 | |
| 232 | + gpgkey=$FORGE_URL/pkg/_keys/rpm/$OWNER | |
| 233 | + EOF |
modified
Cargo.toml
+39
−0
| 3 | 3 | version = "0.1.0" |
| 4 | 4 | edition = "2021" |
| 5 | 5 | description = "Application icon designer for the Casabeza and neiam palettes" |
| 6 | +authors = ["Gabe <gabe@neiam.org>"] | |
| 7 | +license = "GPL-3.0-or-later" | |
| 8 | + | |
| 9 | +# Packaging. Both tools read the release binary out of target/release, so | |
| 10 | +# `cargo build --release` has to have run first — `cargo deb --no-build` | |
| 11 | +# and `cargo generate-rpm` only assemble. | |
| 12 | +# | |
| 13 | +# This is a GUI application, so unlike markprompter it also installs a | |
| 14 | +# .desktop entry and hicolor icons; without them it would install and | |
| 15 | +# then be invisible in every application menu. The icon is generated by | |
| 16 | +# the studio itself from presets/icon-studio.toml. | |
| 17 | +[package.metadata.deb] | |
| 18 | +name = "neiam-icon-studio" | |
| 19 | +section = "graphics" | |
| 20 | +assets = [ | |
| 21 | + # target/release is special: cargo-deb rewrites it to the real | |
| 22 | + # target directory. | |
| 23 | + ["target/release/icon-studio", "usr/bin/", "755"], | |
| 24 | + ["icon-studio.desktop", "usr/share/applications/", "644"], | |
| 25 | + ["assets/icons/icon-studio-48.png", "usr/share/icons/hicolor/48x48/apps/icon-studio.png", "644"], | |
| 26 | + ["assets/icons/icon-studio-64.png", "usr/share/icons/hicolor/64x64/apps/icon-studio.png", "644"], | |
| 27 | + ["assets/icons/icon-studio-128.png", "usr/share/icons/hicolor/128x128/apps/icon-studio.png", "644"], | |
| 28 | + ["assets/icons/icon-studio-256.png", "usr/share/icons/hicolor/256x256/apps/icon-studio.png", "644"], | |
| 29 | + ["README.md", "usr/share/doc/neiam-icon-studio/README.md", "644"], | |
| 30 | + ["presets/*.toml", "usr/share/neiam-icon-studio/presets/", "644"], | |
| 31 | +] | |
| 32 | + | |
| 33 | +[package.metadata.generate-rpm] | |
| 34 | +name = "neiam-icon-studio" | |
| 35 | +assets = [ | |
| 36 | + { source = "target/release/icon-studio", dest = "/usr/bin/icon-studio", mode = "0755" }, | |
| 37 | + { source = "icon-studio.desktop", dest = "/usr/share/applications/icon-studio.desktop", mode = "0644" }, | |
| 38 | + { source = "assets/icons/icon-studio-48.png", dest = "/usr/share/icons/hicolor/48x48/apps/icon-studio.png", mode = "0644" }, | |
| 39 | + { source = "assets/icons/icon-studio-64.png", dest = "/usr/share/icons/hicolor/64x64/apps/icon-studio.png", mode = "0644" }, | |
| 40 | + { source = "assets/icons/icon-studio-128.png", dest = "/usr/share/icons/hicolor/128x128/apps/icon-studio.png", mode = "0644" }, | |
| 41 | + { source = "assets/icons/icon-studio-256.png", dest = "/usr/share/icons/hicolor/256x256/apps/icon-studio.png", mode = "0644" }, | |
| 42 | + { source = "README.md", dest = "/usr/share/doc/neiam-icon-studio/README.md", doc = true, mode = "0644" }, | |
| 43 | + { source = "presets/*.toml", dest = "/usr/share/neiam-icon-studio/presets/", mode = "0644" }, | |
| 44 | +] | |
| 6 | 45 | |
| 7 | 46 | [lib] |
| 8 | 47 | name = "icon_studio" |
modified
README.md
+28
−0
| 116 | 116 | for text marks and sub-banners. Rendering text through the Symbols font |
| 117 | 117 | lands on `.notdef`, which is why text needs its own face. |
| 118 | 118 | |
| 119 | +## Installing | |
| 120 | + | |
| 121 | +Packages are published to this forge's registries on every push to master, | |
| 122 | +x86_64 only. | |
| 123 | + | |
| 124 | +```sh | |
| 125 | +# Debian / Ubuntu | |
| 126 | +curl -fsSL $FORGE/pkg/_keys/deb/neiam | sudo tee /etc/apt/keyrings/neiam.asc >/dev/null | |
| 127 | +echo "deb [signed-by=/etc/apt/keyrings/neiam.asc] $FORGE/pkg/deb/neiam/_all stable main" \ | |
| 128 | + | sudo tee /etc/apt/sources.list.d/neiam.list | |
| 129 | +sudo apt update && sudo apt install neiam-icon-studio | |
| 130 | +``` | |
| 131 | + | |
| 132 | +For Fedora / RHEL / openSUSE the workflow prints a matching `.repo` stanza at | |
| 133 | +the end of each run. | |
| 134 | + | |
| 135 | +The package installs the binary, a desktop entry with the application icon, | |
| 136 | +and the preset documents under `/usr/share/neiam-icon-studio/presets/`. | |
| 137 | + | |
| 138 | +Builds off a tag publish the bare Cargo version; builds off master publish | |
| 139 | +`<version>+dev.<timestamp>`, which sorts *after* the release — so a machine | |
| 140 | +tracking this repository gets the latest build, and two runs never collide on | |
| 141 | +one filename. | |
| 142 | + | |
| 119 | 143 | ## Running |
| 120 | 144 | |
| 121 | 145 | ``` |
| 124 | 148 | cargo run --example reproduce_cbz # re-render the five cbz icons |
| 125 | 149 | cargo run --example showcase # every background generator |
| 126 | 150 | cargo run --example make_presets # regenerate presets/ |
| 151 | +cargo run --example make_app_icon # regenerate the desktop icon | |
| 127 | 152 | ``` |
| 128 | 153 | |
| 154 | +The application's own icon is made by the application, from | |
| 155 | +`presets/icon-studio.toml` — change it in the studio and regenerate. | |
| 156 | + | |
| 129 | 157 | Export writes `ic_icon`, `ic_icon_background` and `ic_icon_foreground` across |
| 130 | 158 | `mipmap-mdpi` through `mipmap-xxxhdpi` (108–432px), matching the layout the |
| 131 | 159 | cbz apps use. |
modified
?
+0
−0
Binary file — not rendered.
modified
?
+0
−0
Binary file — not rendered.
modified
?
+0
−0
Click to load diff…
modified
?
+0
−0
Click to load diff…
added
examples/make_app_icon.rs
+22
−0
Click to load diff…
modified
examples/make_presets.rs
+22
−8
Click to load diff…
modified
examples/reproduce_cbz.rs
+16
−7
Click to load diff…
modified
examples/showcase.rs
+12
−3
Click to load diff…
added
icon-studio.desktop
+13
−0
Click to load diff…
added
presets/icon-studio.toml
+41
−0
Click to load diff…
modified
src/app.rs
+24
−6
Click to load diff…
modified
src/doc.rs
+17
−4
Click to load diff…
modified
src/export.rs
+7
−2
Click to load diff…
modified
src/icons.rs
+1
−7
Click to load diff…
modified
src/palette/generated.rs
+560
−140
Click to load diff…
modified
src/palette/mod.rs
+16
−3
Click to load diff…
modified
src/render.rs
+52
−12
Click to load diff…
modified
src/spec.rs
+12
−3
Click to load diff…
modified
tests/documents.rs
+40
−9
Click to load diff…
modified
tests/reproduce.rs
+37
−13
Click to load diff…
Parents: f232944