diff --git a/.gitignore b/.gitignore index 5e55dbd..3ef46ec 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,13 @@ /target /target-appimage +/target-deb /.cache /dist/*.AppImage /dist/rcalc.AppDir/ +/dist/deb-root/ +/dist/*.deb +/dist/Packages +/dist/Packages.gz /dist/.glibc-build.txt .rcalc_last_bump /target-windows diff --git a/README.md b/README.md index e5cbefc..1ec8dab 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,70 @@ cargo run --release ./dist/rcalc ``` +## Install as a Debian package + +```bash +./scripts/build-deb.sh # → dist/rcalc_-_.deb +sudo apt install ./dist/rcalc_*_amd64.deb +rcalc # or start it from the application menu +``` + +The binary is compiled in an Ubuntu 22.04 container (Docker) by default, so the +package keeps a glibc 2.35 baseline and installs on Ubuntu 22.04 / 24.04 / 26.04. +`Depends:` are generated from the ELF with `dpkg-shlibdeps`. + +Menu/taskbar icons come from `ico/` — every PNG there is placed into the +hicolor slot matching its real pixel size (file names do not matter), and the +remaining smaller sizes are downsampled from the biggest one. Without `ico/` +the icons are resized from `assets/icon.png`. The icon inside the window +(`ui/app.slint`) and the Windows `rcalc.exe` icon still use `assets/`. + +Useful switches: + +| Variable | Effect | +| --- | --- | +| `DEB_REVISION=` | packaging revision, `1` by default → `Version: 0.1.28-1` | +| `REPO=1` | also write `dist/Packages(.gz)` so `dist/` works as a local apt repo | +| `SKIP_DOCKER=1` | build on the host (needs this host's glibc on the target) | +| `PREBUILT=1` | package the existing `dist/rcalc`, no compile | +| `NO_STRIP=1` | keep symbols (bigger package) | +| `KEEP_STAGE=1` | leave `dist/deb-root/` for inspection | +| `DOCKER_IMAGE=` | other build image, e.g. `debian:12` for an older baseline | +| `DEB_MAINTAINER=` | `"Name "` in `Maintainer:` (default: git user) | + +### Version and updates + +Package version = `-`, e.g. `0.1.28-1`. + +* changed app code → bump `version` in `Cargo.toml` (it is also the window title); +* changed only packaging (icon, `.desktop`, `Depends`, rebuilt binary) → `DEB_REVISION=2`. + +`dpkg` compares these in order (`0.1.28-1 < 0.1.28-2 < 0.1.29-1`, and +`0.1.9 < 0.1.10`), so every new build is an upgrade, never a downgrade. The +script prints the resulting upgrade path and warns if the version being built is +not newer than the installed one — an `apt upgrade` of such a package does nothing. +Release notes live in `packaging/deb/changelog` and are installed as +`/usr/share/doc/rcalc/changelog.Debian.gz`. + +Update in place (config in `~/.config/rcalc/config.json` is a user file, it survives): + +```bash +sudo apt install ./dist/rcalc_0.1.28-2_amd64.deb +``` + +Or keep `dist/` as a local repository and update with plain `apt` (build with +`REPO=1`, then point apt at the folder): + +```bash +echo 'deb [trusted=yes] file:/path/to/RCalc/dist ./' | sudo tee /etc/apt/sources.list.d/rcalc.list +sudo apt update && sudo apt install rcalc # later: sudo apt upgrade +``` + +> If `scripts/install-desktop.sh` was ever run, `~/.local/bin/rcalc` (a symlink to +> `dist/rcalc`) shadows `/usr/bin/rcalc` — remove it after switching to the package, +> otherwise `rcalc` in the terminal still starts the old binary. The script warns +> about this during the build. + ### Icon (Linux / Wayland) On **Wayland** the window `icon` property is ignored — the desktop uses @@ -27,8 +91,9 @@ clicking a random binary path, so KDE/GNOME can match `app_id=rcalc` to ## Features -- Modes: **Standard**, **Programmer**, **Convert** -- Standard: `+ − × ÷`, `%`, `±`, `1/x`, `x²`, `√x`, memory +- Modes: **Standard**, **Engineering**, **Programmer**, **Convert**, **CRC**, **Settings** +- Standard: `+ − × ÷`, `%`, `±`, `1/x`, `x²`, `√x`, memory, formula panel +- Engineering: trig functions, log/pow, DEG/RAD, SI prefixes - Programmer: simultaneous **HEX / DEC / OCT / BIN**, sizes **QWORD / DWORD / DINT / WORD / INT / BYTE**, bitwise ops, clickable bits (WORD/DWORD — unsigned DEC; INT/DINT — signed) @@ -46,3 +111,7 @@ clicking a random binary path, so KDE/GNOME can match `app_id=rcalc` to - `src/main.rs` — UI wiring - `ui/app.slint` — Slint interface - `dist/rcalc` — release binary +- `scripts/build-deb.sh` + `packaging/deb/` — .deb package (control, desktop + file, AppStream metainfo, maintainer scripts) +- `scripts/build-appimage.sh` — portable AppImage +- `scripts/install-desktop.sh` — per-user launcher + icon (no root needed) diff --git a/dist/rcalc b/dist/rcalc index 93b60c4..834fc10 100755 Binary files a/dist/rcalc and b/dist/rcalc differ diff --git a/ico/Calculator_icon-icons.com_54972 (1).png b/ico/Calculator_icon-icons.com_54972 (1).png new file mode 100644 index 0000000..56cc99b Binary files /dev/null and b/ico/Calculator_icon-icons.com_54972 (1).png differ diff --git a/ico/Calculator_icon-icons.com_54972 (2).png b/ico/Calculator_icon-icons.com_54972 (2).png new file mode 100644 index 0000000..7793e88 Binary files /dev/null and b/ico/Calculator_icon-icons.com_54972 (2).png differ diff --git a/ico/Calculator_icon-icons.com_54972 (3).png b/ico/Calculator_icon-icons.com_54972 (3).png new file mode 100644 index 0000000..a047aad Binary files /dev/null and b/ico/Calculator_icon-icons.com_54972 (3).png differ diff --git a/ico/Calculator_icon-icons.com_54972 (4).png b/ico/Calculator_icon-icons.com_54972 (4).png new file mode 100644 index 0000000..21b1d59 Binary files /dev/null and b/ico/Calculator_icon-icons.com_54972 (4).png differ diff --git a/ico/Calculator_icon-icons.com_54972 (5).png b/ico/Calculator_icon-icons.com_54972 (5).png new file mode 100644 index 0000000..1343eac Binary files /dev/null and b/ico/Calculator_icon-icons.com_54972 (5).png differ diff --git a/ico/Calculator_icon-icons.com_54972.png b/ico/Calculator_icon-icons.com_54972.png new file mode 100644 index 0000000..1623359 Binary files /dev/null and b/ico/Calculator_icon-icons.com_54972.png differ diff --git a/packaging/deb/changelog b/packaging/deb/changelog new file mode 100644 index 0000000..782e904 --- /dev/null +++ b/packaging/deb/changelog @@ -0,0 +1,12 @@ +rcalc (0.1.28-1) unstable; urgency=medium + + * Upstream 0.1.28 (version from Cargo.toml), first Debian packaging. + * Binary is built in an Ubuntu 22.04 container (glibc 2.35 baseline), so the + package installs on Ubuntu 22.04 / 24.04 / 26.04. + * Ships /usr/bin/rcalc, /usr/share/applications/rcalc.desktop, hicolor icons + taken from ico/, AppStream metainfo and postinst/postrm cache refresh hooks. + * Depends are generated from the ELF with dpkg-shlibdeps. + * Build fix: clippy::approx_constant error in the convert round-trip test + (3.1415927 -> std::f32::consts::PI). + + -- alexMB5300 Thu, 17 Sep 2026 19:12:31 +0300 diff --git a/packaging/deb/control.in b/packaging/deb/control.in new file mode 100644 index 0000000..def7fbe --- /dev/null +++ b/packaging/deb/control.in @@ -0,0 +1,23 @@ +Package: rcalc +Version: @VERSION@ +Architecture: @ARCHITECTURE@ +Maintainer: @MAINTAINER@ +Section: math +Priority: optional +Installed-Size: @INSTALLED_SIZE@ +Depends: @DEPENDS@ +Recommends: libegl1, libgl1, libx11-6, libwayland-client0 +Description: desktop calculator with programmer, engineering and convert tools + Calculator inspired by the Windows Calculator (standard mode), written in + Rust with the Slint UI toolkit. Modes: Standard, Engineering, Programmer, + Convert, CRC and Settings. + . + Programmer mode shows HEX / DEC / OCT / BIN at once, with QWORD / DWORD / + DINT / WORD / INT / BYTE word sizes, bitwise operators and clickable bits. + . + Convert mode maps a float32 to two 16-bit PLC words in every byte order + (AB CD / CD AB / BA DC / DC BA), approximates electronic gear ratios as + num/den and converts Char / Hex / Dec ASCII codes. + . + CRC mode computes Modbus RTU CRC-16 and Modbus ASCII LRC from a string of + hex bytes, e.g. "01 03 00 00 00 0A". diff --git a/packaging/deb/postinst b/packaging/deb/postinst new file mode 100755 index 0000000..df02a93 --- /dev/null +++ b/packaging/deb/postinst @@ -0,0 +1,31 @@ +#!/bin/sh +# Maintainer script of the generated rcalc .deb (see scripts/build-deb.sh). +set -e + +case "$1" in + configure) + ;; + abort-upgrade|abort-remove|abort-deconfigure) + exit 0 + ;; + *) + echo "postinst called with unknown argument \`$1'" >&2 + exit 1 + ;; +esac + +# Refresh the XDG caches. Never fail the installation because of them: the +# tools may be absent (minimal systems, chroot, dpkg -x in a container). +if command -v update-desktop-database >/dev/null 2>&1; then + update-desktop-database -q /usr/share/applications || true +fi + +if command -v gtk-update-icon-cache >/dev/null 2>&1; then + gtk-update-icon-cache -q -t -f /usr/share/icons/hicolor || true +fi + +if command -v appstreamcli >/dev/null 2>&1; then + appstreamcli refresh --force >/dev/null 2>&1 || true +fi + +exit 0 diff --git a/packaging/deb/postrm b/packaging/deb/postrm new file mode 100755 index 0000000..0a66871 --- /dev/null +++ b/packaging/deb/postrm @@ -0,0 +1,27 @@ +#!/bin/sh +# Maintainer script of the generated rcalc .deb (see scripts/build-deb.sh). +set -e + +case "$1" in + remove|upgrade|disappear|failed-upgrade|abort-install|abort-upgrade|purge) + ;; + *) + echo "postrm called with unknown argument \`$1'" >&2 + exit 1 + ;; +esac + +# Rebuild the caches after the files were (or are about to be) removed. +if command -v update-desktop-database >/dev/null 2>&1; then + update-desktop-database -q /usr/share/applications || true +fi + +if command -v gtk-update-icon-cache >/dev/null 2>&1; then + gtk-update-icon-cache -q -t -f /usr/share/icons/hicolor || true +fi + +if command -v appstreamcli >/dev/null 2>&1; then + appstreamcli refresh --force >/dev/null 2>&1 || true +fi + +exit 0 diff --git a/packaging/deb/rcalc.appdata.xml.in b/packaging/deb/rcalc.appdata.xml.in new file mode 100644 index 0000000..c89f33a --- /dev/null +++ b/packaging/deb/rcalc.appdata.xml.in @@ -0,0 +1,35 @@ + + + + rcalc.desktop + CC0-1.0 + + NOASSERTION + Rcalc + Calculator with programmer, engineering, convert and CRC modes + +

+ Desktop calculator inspired by the Windows Calculator. Standard mode uses + exact rational arithmetic, so 0.1 + 0.2 really gives 0.3. +

+
    +
  • Standard: + − × ÷, %, ±, 1/x, x², √x, memory, formula panel
  • +
  • Engineering: trigonometry, log/pow, DEG/RAD, SI prefixes
  • +
  • Programmer: simultaneous HEX / DEC / OCT / BIN, QWORD … BYTE word + sizes, bitwise ops, clickable bits
  • +
  • Convert: float32 to two 16-bit words (AB CD / CD AB / BA DC / DC BA), + gear ratio num/den, ASCII codes
  • +
  • CRC: Modbus RTU CRC-16 and Modbus ASCII LRC from hex bytes
  • +
  • Full keyboard input, three themes, three button sizes
  • +
+
+ + Utility + Calculator + Science + + + + + +
diff --git a/packaging/deb/rcalc.desktop b/packaging/deb/rcalc.desktop new file mode 100644 index 0000000..306d40d --- /dev/null +++ b/packaging/deb/rcalc.desktop @@ -0,0 +1,13 @@ +[Desktop Entry] +Type=Application +Name=Rcalc +GenericName=Calculator +Comment=Calculator with Programmer, Engineering, Convert and CRC tools +Exec=rcalc +Icon=rcalc +Terminal=false +Categories=Utility;Calculator; +Keywords=calculator;programmer;hex;bitwise;modbus;crc;lrc; +StartupNotify=true +# Wayland app_id / X11 WM_CLASS — must match set_xdg_app_id("rcalc") in src/main.rs +StartupWMClass=rcalc diff --git a/scripts/build-deb.sh b/scripts/build-deb.sh new file mode 100755 index 0000000..f97e170 --- /dev/null +++ b/scripts/build-deb.sh @@ -0,0 +1,439 @@ +#!/usr/bin/env bash +# Build a Debian package (.deb) for rcalc. +# +# By default the release binary is compiled inside Ubuntu 22.04 (glibc 2.35) — +# the same baseline and the same cargo cache (target-appimage) as +# scripts/build-appimage.sh — so the package installs on Ubuntu 22.04 / 24.04 +# and not only on this rolling host distro. +# +# Package layout (FHS): +# /usr/bin/rcalc stripped release binary +# /usr/share/applications/rcalc.desktop launcher (Exec=rcalc) +# /usr/share/icons/hicolor/*x*/apps/rcalc.png icons from ico/ (or assets/) +# /usr/share/metainfo/rcalc.appdata.xml AppStream metadata +# /usr/share/doc/rcalc/README.md +# +# Env switches: +# DEB_REVISION packaging revision appended to the Cargo.toml version +# (0.1.28 + 1 → "Version: 0.1.28-1"); bump it for updates that +# change only the package, not the app +# REPO=1 also generate dist/Packages(.gz) → dist/ usable as an apt repo +# SKIP_DOCKER=1 build on the host instead of Docker (fast, but the deb will +# then require this host's glibc — see DEPENDS below) +# PREBUILT=1 do not compile, package the existing dist/rcalc as-is +# NO_STRIP=1 keep symbols in the binary (much bigger package) +# KEEP_STAGE=1 leave dist/deb-root/ around for inspection +# DOCKER_IMAGE build container image (default: ubuntu:22.04) +# DEB_MAINTAINER "Name " (default: git user.name/user.email) +set -euo pipefail + +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +PKG="rcalc" +OUT_DIR="${OUT_DIR:-${ROOT}/dist}" +META_DIR="${ROOT}/packaging/deb" +STAGE="${OUT_DIR}/deb-root" +ARCH="$(dpkg --print-architecture 2>/dev/null || uname -m)" +VERSION="$(grep -m1 '^version' "${ROOT}/Cargo.toml" | sed 's/.*"\(.*\)".*/\1/')" +# Debian version = upstream (Cargo.toml) + packaging revision: "0.1.28-1". +# The revision lets you ship an update (fixed desktop file, new icon, rebuilt +# binary) WITHOUT touching Cargo.toml; dpkg compares 0.1.28-1 < 0.1.28-2 < +# 0.1.29-1 correctly. DEB_REVISION is the knob for that. +DEB_REVISION="${DEB_REVISION:-1}" +DEB_VERSION="${VERSION}-${DEB_REVISION}" +DEB_FILE="${OUT_DIR}/${PKG}_${DEB_VERSION}_${ARCH}.deb" + +DOCKER_IMAGE="${DOCKER_IMAGE:-ubuntu:22.04}" +SKIP_DOCKER="${SKIP_DOCKER:-0}" +PREBUILT="${PREBUILT:-0}" +NO_STRIP="${NO_STRIP:-0}" +KEEP_STAGE="${KEEP_STAGE:-0}" +# Nested $( ) with quotes inside ${:- } is parsed unreliably by bash — resolve first. +GIT_NAME="$(git -C "${ROOT}" config user.name 2>/dev/null || true)" +GIT_MAIL="$(git -C "${ROOT}" config user.email 2>/dev/null || true)" +MAINTAINER="${DEB_MAINTAINER:-${GIT_NAME:-${PKG}-packaging} <${GIT_MAIL:-localhost}>}" + +need_cmd() { + command -v "$1" >/dev/null 2>&1 || { + echo "error: required command not found: $1" >&2 + exit 1 + } +} + +# ---------------------------------------------------------------- binary ---- + +build_binary_docker() { + need_cmd docker + echo "Building release binary in ${DOCKER_IMAGE} (glibc 2.35 baseline)…" + mkdir -p "${HOME}/.cargo/registry" "${HOME}/.cargo/git" + docker run --rm \ + -v "${ROOT}:/src" \ + -v "${HOME}/.cargo/registry:/root/.cargo/registry" \ + -v "${HOME}/.cargo/git:/root/.cargo/git" \ + -w /src \ + "${DOCKER_IMAGE}" \ + bash -lc ' + set -euo pipefail + export DEBIAN_FRONTEND=noninteractive + apt-get update -qq + apt-get install -y -qq curl build-essential pkg-config libfontconfig1-dev ca-certificates + curl --proto "=https" --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable --profile minimal + # shellcheck disable=SC1091 + source "$HOME/.cargo/env" + export CARGO_TARGET_DIR=/src/target-appimage + cargo build --release + install -D -m755 /src/target-appimage/release/rcalc /src/dist/rcalc + ldd --version | head -1 > /src/dist/.glibc-build.txt + # Make artifacts writable by the host user + chown -R "$(id -u):$(id -g)" /src/dist/rcalc /src/dist/.glibc-build.txt /src/target-appimage || true + ' +} + +build_binary_host() { + echo "Building release binary on the host ($(ldd --version | head -1))…" + (cd "${ROOT}" && cargo build --release) + install -D -m755 "${ROOT}/target/release/${PKG}" "${OUT_DIR}/${PKG}" + ldd --version | head -1 > "${OUT_DIR}/.glibc-build.txt" || true +} + +ensure_binary() { + mkdir -p "${OUT_DIR}" + if [[ "${PREBUILT}" == "1" ]]; then + echo "PREBUILT=1: packaging ${OUT_DIR}/${PKG} as-is" + elif [[ "${SKIP_DOCKER}" == "1" ]]; then + build_binary_host + else + build_binary_docker + fi + + if [[ ! -x "${OUT_DIR}/${PKG}" ]]; then + echo "error: ${OUT_DIR}/${PKG} missing — run without PREBUILT=1" >&2 + exit 1 + fi +} + +# ---------------------------------------------------------------- staging --- + +stage_tree() { + rm -rf "${STAGE}" + mkdir -p \ + "${STAGE}/DEBIAN" \ + "${STAGE}/usr/bin" \ + "${STAGE}/usr/share/applications" \ + "${STAGE}/usr/share/metainfo" \ + "${STAGE}/usr/share/doc/${PKG}" + + install -m755 "${OUT_DIR}/${PKG}" "${STAGE}/usr/bin/${PKG}" + if [[ "${NO_STRIP}" != "1" ]] && command -v strip >/dev/null 2>&1; then + strip --strip-unneeded "${STAGE}/usr/bin/${PKG}" + fi + + install -m644 "${META_DIR}/${PKG}.desktop" \ + "${STAGE}/usr/share/applications/${PKG}.desktop" + + sed -e "s|@VERSION@|${VERSION}|" \ + -e "s|@RELEASE_DATE@|$(date -u +%Y-%m-%d)|" \ + "${META_DIR}/${PKG}.appdata.xml.in" \ + > "${STAGE}/usr/share/metainfo/${PKG}.appdata.xml" + + gzip -9 -c "${ROOT}/README.md" > "${STAGE}/usr/share/doc/${PKG}/README.md.gz" + echo "Binary: $(file -b "${STAGE}/usr/bin/${PKG}" | cut -d, -f1-3)" +} + +# Icons for the desktop entry: /usr/share/icons/hicolor/x/apps/rcalc.png +# +# Source of truth, in order: +# 1. ico/*.png — ready-made sizes dropped in by hand. The REAL pixel size of +# each file decides which hicolor slot it fills (file names are ignored), +# and ico/ wins for the whole family: mixing ico/ artwork with assets/ +# artwork would show a different picture depending on the icon size. +# 2. assets/icon.png resized to the standard sizes (needs ImageMagick). +# 3. the three PNGs bundled in assets/, as-is. +stage_icons() { + local dst="${STAGE}/usr/share/icons/hicolor" + local prepared=0 master="" master_size=0 size + + local magick="" + for tool in magick convert; do + if command -v "${tool}" >/dev/null 2>&1; then + magick="${tool}" + break + fi + done + + if [[ -d "${ROOT}/ico" ]] && [[ -n "${magick}" ]]; then + for f in "${ROOT}"/ico/*.png; do + [[ -f "${f}" ]] || continue + local w h + read -r w h <<<"$(identify -format '%w %h' "${f}" 2>/dev/null || echo '0 0')" || true + [[ "${w}" == "${h}" ]] && [[ "${w}" -gt 0 ]] || continue + if (( w > master_size )); then + master="${f}" + master_size="${w}" + fi + local slot="${dst}/${w}x${w}/apps/${PKG}.png" + if [[ ! -e "${slot}" ]]; then + mkdir -p "${dst}/${w}x${w}/apps" + install -m644 "${f}" "${slot}" + prepared=$((prepared + 1)) + fi + done + fi + + if (( prepared > 0 )); then + # Fill the remaining standard sizes from the biggest ico/ file — never + # upscale, so a 512px source does not produce a blurry 1024px icon. + if [[ -n "${magick}" ]]; then + for size in 16 24 32 48 64 96 128 256 512; do + [[ -e "${dst}/${size}x${size}/apps/${PKG}.png" ]] && continue + (( size <= master_size )) || continue + mkdir -p "${dst}/${size}x${size}/apps" + "${magick}" "${master}" -resize "${size}x${size}" \ + "${dst}/${size}x${size}/apps/${PKG}.png" + done + fi + echo "Icons: ${prepared} ready-made from ico/ (master ${master_size}px)" + return 0 + fi + + if [[ -n "${magick}" ]]; then + for size in 16 24 32 48 64 128 256 512 1024; do + mkdir -p "${dst}/${size}x${size}/apps" + "${magick}" "${ROOT}/assets/icon.png" -resize "${size}x${size}" \ + "${dst}/${size}x${size}/apps/${PKG}.png" + done + echo "Icons: resized from assets/icon.png (1024px master)" + else + install -D -m644 "${ROOT}/assets/icon-64.png" "${dst}/64x64/apps/${PKG}.png" + install -D -m644 "${ROOT}/assets/icon-256.png" "${dst}/256x256/apps/${PKG}.png" + install -D -m644 "${ROOT}/assets/icon.png" "${dst}/1024x1024/apps/${PKG}.png" + echo "Icons: assets/*.png as-is (ImageMagick not found)" + fi +} + +# ------------------------------------------------------------ dependencies -- + +# Real library dependencies, read from the ELF itself. GL / X11 / Wayland +# libraries are dlopen()ed by winit at runtime, so they never show up in +# NEEDED — they are listed in Recommends in control.in instead. +compute_depends() { + local depends="" + if [[ "${USE_SHLIBDEPS:-1}" == "1" ]] && command -v dpkg-shlibdeps >/dev/null 2>&1; then + local tmp + tmp="$(mktemp -d)" + mkdir -p "${tmp}/debian" + # dpkg-shlibdeps insists on reading a debian/control next to it. + cat > "${tmp}/debian/control" < "${tmp}/debian/${PKG}.substvars" + (cd "${tmp}" && dpkg-shlibdeps -T"debian/${PKG}.substvars" "${STAGE}/usr/bin/${PKG}") \ + >/dev/null 2>&1 || true + depends="$(sed -n 's/^shlibs:Depends=//p' "${tmp}/debian/${PKG}.substvars")" + rm -rf "${tmp}" + fi + + if [[ -z "${depends}" ]]; then + echo "warning: dpkg-shlibdeps gave nothing, using a hand-written Depends list" >&2 + depends='libc6, libfontconfig1, libgcc-s1' + fi + printf '%s' "${depends}" +} + +# ---------------------------------------------------------------- control --- + +stage_control() { + local installed_size depends + installed_size="$(du -s -k --exclude="${STAGE}/DEBIAN" "${STAGE}" | awk '{print $1}')" + depends="$(compute_depends)" + + sed -e "s|@VERSION@|${DEB_VERSION}|" \ + -e "s|@ARCHITECTURE@|${ARCH}|" \ + -e "s|@MAINTAINER@|${MAINTAINER}|" \ + -e "s|@INSTALLED_SIZE@|${installed_size}|" \ + -e "s|@DEPENDS@|${depends}|" \ + "${META_DIR}/control.in" > "${STAGE}/DEBIAN/control" + + # Maintainer scripts must be executables owned by root in the archive. + install -m755 "${META_DIR}/postinst" "${STAGE}/DEBIAN/postinst" + install -m755 "${META_DIR}/postrm" "${STAGE}/DEBIAN/postrm" + + (cd "${STAGE}" && find . -path ./DEBIAN -prune -o -type f -print \ + | sed 's|^\./||' | sort | xargs -r md5sum) > "${STAGE}/DEBIAN/md5sums" + + echo "Version: ${DEB_VERSION} (upstream ${VERSION} + revision ${DEB_REVISION})" + echo "Architecture: ${ARCH}" + echo "Maintainer: ${MAINTAINER}" + echo "Installed-Size: ${installed_size} KB" + echo "Depends: ${depends}" +} + + +# ------------------------------------------------------- version / update --- + +# What "apt upgrade" needs: a version that is strictly greater than the one +# already installed, and a package name that does not change. Both are checked +# here so a release is never silently unpromotable. +check_version() { + case "${VERSION}" in + [0-9]*) ;; + *) echo "error: upstream version '${VERSION}' must start with a digit (dpkg rule)" >&2; exit 1 ;; + esac + if [[ ! "${VERSION}" =~ ^[0-9A-Za-z.+-~:]+$ || ! "${DEB_REVISION}" =~ ^[0-9A-Za-z.+-~]+$ ]]; then + echo "error: bad version '${DEB_VERSION}' — allowed chars are letters, digits and . + - ~ :" >&2 + exit 1 + fi + + local installed + installed="$(dpkg-query -W -f='${Version}' "${PKG}" 2>/dev/null || true)" + if [[ -n "${installed}" ]]; then + if dpkg --compare-versions "${installed}" ge "${DEB_VERSION}"; then + echo "warning: ${PKG} ${installed} is ALREADY installed and is not older than ${DEB_VERSION}" >&2 + echo " → 'apt upgrade' will do nothing. Bump Cargo.toml or DEB_REVISION." >&2 + else + echo "Upgrade path: ${installed} → ${DEB_VERSION} (apt upgrade will pick this up)" + fi + else + echo "Upgrade path: fresh install of ${DEB_VERSION}" + fi + + # scripts/install-desktop.sh links ~/.local/bin/rcalc → dist/rcalc, and + # ~/.local/bin comes before /usr/bin in PATH: the packaged binary would be + # shadowed by an old copy even after a successful upgrade. + if [[ -e "${HOME}/.local/bin/${PKG}" ]]; then + echo "warning: ${HOME}/.local/bin/${PKG} exists and shadows /usr/bin/${PKG}" >&2 + echo " (installed by scripts/install-desktop.sh — remove it after switching to the .deb)" >&2 + fi +} + +# /usr/share/doc/rcalc/changelog.Debian.gz — Debian policy expects a changelog, +# and it is the human-readable history of what every version changed. +# packaging/deb/changelog (kept in git) wins; otherwise one entry is generated +# from the current commit so the package is never changelog-less. +stage_changelog() { + local doc="${STAGE}/usr/share/doc/${PKG}" + mkdir -p "${doc}" + + local plain="${doc}/changelog.Debian" # gzip → changelog.Debian.gz (Debian-side history) + if [[ -f "${META_DIR}/changelog" ]]; then + cp "${META_DIR}/changelog" "${plain}" + echo "Changelog: packaging/deb/changelog" + else + local githash gitsubject + githash="$(git -C "${ROOT}" rev-parse --short HEAD 2>/dev/null || echo unknown)" + gitsubject="$(git -C "${ROOT}" log -1 --pretty=%s 2>/dev/null || echo packaging)" + { + printf '%s (%s) unstable; urgency=medium\n\n' "${PKG}" "${DEB_VERSION}" + printf ' * Upstream %s, packaged as %s.\n' "${VERSION}" "${DEB_VERSION}" + if [[ -f "${OUT_DIR}/.glibc-build.txt" ]]; then + printf ' * Built against %s\n' "$(cat "${OUT_DIR}/.glibc-build.txt")" + fi + printf ' * git %s: %s\n\n' "${githash}" "${gitsubject}" + printf ' -- %s %s\n' "${MAINTAINER}" "$(date -R)" + } > "${plain}" + echo "Changelog: generated (add packaging/deb/changelog to keep real history)" + fi + + gzip -9n "${plain}" # -n: no timestamp → reproducible .gz + rm -f "${plain}" +} + +# REPO=1 → turn dist/ into a flat apt repository, so updates can be installed +# with plain `apt update && apt upgrade` instead of pointing at a .deb file. +make_repo() { + [[ "${REPO:-0}" == "1" ]] || return 0 + need_cmd dpkg-scanpackages + ( + cd "${OUT_DIR}" + # One pass: Packages is the index, Packages.gz the compressed copy apt reads. + # Keep both files: apt reads Packages.gz, humans diff/read Packages. + dpkg-scanpackages --multiversion . /dev/null > Packages 2>/dev/null + gzip -9n -c Packages > Packages.gz + ) + echo "Repo index: ${OUT_DIR}/Packages.gz ($(grep -c '^Package:' "${OUT_DIR}/Packages") entries)" + echo " apt one-liner: echo 'deb [trusted=yes] file:${OUT_DIR} ./' \\" + echo " | sudo tee /etc/apt/sources.list.d/${PKG}.list" + echo " then: sudo apt update && sudo apt install ${PKG}" +} + +# ------------------------------------------------------------------ build --- + +build_package() { + need_cmd dpkg-deb + rm -f "${DEB_FILE}" + + # The stage tree inherits the build user's umask (often 002 → group-writable). + # Debian expects 755 dirs / 644 data files, plus the exec bits on the binary + # and the maintainer scripts. + find "${STAGE}" -type d -exec chmod 0755 {} + + find "${STAGE}" -type f -exec chmod 0644 {} + + chmod 0755 "${STAGE}/usr/bin/${PKG}" \ + "${STAGE}/DEBIAN/postinst" "${STAGE}/DEBIAN/postrm" + + # --root-owner-group: the stage tree is owned by the build user, the archive + # must not be (dpkg rejects foreign uids unless forced). + dpkg-deb --root-owner-group --build "${STAGE}" "${DEB_FILE}" +} + +verify_package() { + local ok=1 + + # Fields straight from the archive (dpkg-deb --info output is localised). + dpkg-deb --field "${DEB_FILE}" + + if command -v desktop-file-validate >/dev/null 2>&1; then + if desktop-file-validate "${STAGE}/usr/share/applications/${PKG}.desktop"; then + echo "desktop-file-validate: OK" + else + echo "warning: the desktop file did not validate" >&2 + ok=0 + fi + fi + + # dist/rcalc can be stale: build.rs embeds APP_VERSION from Cargo.toml only at + # compile time, and the AppImage packaging had exactly that bug (old binary in + # a new image). Anything newer than the packaged binary means "rebuild it". + local stale + stale="$(find "${ROOT}/src" "${ROOT}/ui" "${ROOT}/build.rs" "${ROOT}/Cargo.toml" \ + -newer "${OUT_DIR}/${PKG}" -print -quit 2>/dev/null || true)" + if [[ -n "${stale}" ]]; then + echo "warning: ${stale} is newer than the packaged binary" >&2 + echo " (PREBUILT=1 / SKIP_DOCKER=1 reused an old dist/${PKG} — rebuild to be safe)" >&2 + ok=0 + fi + + echo "Files in package: $(dpkg-deb -c "${DEB_FILE}" | grep -c '^-')" + + if [[ "${KEEP_STAGE}" != "1" ]]; then + rm -rf "${STAGE}" + fi + + echo + ls -l "${DEB_FILE}" + [[ "${ok}" == "1" ]] || echo "(check the warnings above)" +} + +check_version +ensure_binary +stage_tree +stage_icons +stage_changelog +stage_control +build_package +make_repo +verify_package + +echo +echo "OK: ${DEB_FILE}" +echo "Version: ${DEB_VERSION} (upstream ${VERSION} + revision ${DEB_REVISION})" +echo "Install: sudo apt install '${DEB_FILE}'" +echo "Next release: bump version in Cargo.toml (app changes) — or just" +echo " DEB_REVISION=2 ./scripts/build-deb.sh (packaging/icon only)," +echo " then install the new .deb: apt replaces the old one in place." +echo "Run: rcalc (or start it from the application menu)" +echo "Remove: sudo apt remove ${PKG}" + diff --git a/src/convert.rs b/src/convert.rs index e43cfcb..dca3472 100644 --- a/src/convert.rs +++ b/src/convert.rs @@ -858,7 +858,7 @@ mod tests { #[test] fn roundtrip_all_endians() { - let samples = [0.0f32, -1.0, 3.1415927, 12345.678, f32::MIN_POSITIVE]; + let samples = [0.0f32, -1.0, std::f32::consts::PI, 12345.678, f32::MIN_POSITIVE]; for e in Endian::all() { for &f in &samples { let (w0, w1) = float_to_words(f, e);