Changelog
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
Unreleased
4.0.1 - 2026-09-03
Fixed
verify-appimage-reproducible-build.shnow builds the bundledappimageruntime module from the local checkout instead of resolving it from PyPI, so it tests the current code instead of failing on unreleased versionsappimagetool and the runtime file are now resolved from their newest genuine, immutable, versioned GitHub release instead of the
continuoustag, which GitHub overwrites in place on every upstream rebuild and so could leave a pinned hash permanently unfetchable - existingappimagetool_sha256/runtime_sha256pins keep working unchanged;update-toolspicks up the safer resolution next time it’s runAdded
runtime_version, an informational label alongsideruntime_sha256recording the resolved release tag (the runtime stub has no--versionof its own the way appimagetool does) - written automatically byinit
4.0.0 - 2026-09-03
Added
check/lock/buildnow warn whenpylock/build_pylockpin a package with no prebuilt wheelCI now verifies two independent AppImage builds are byte-identical on every push/PR
check/buildnow warn whenpythonisn’t set and norequires-pythonis in[project], naming the version it defaulted to
Changed
Breaking:
buildis now a required subcommand, consistent withcheck/init/lock/etc.; the barepython -m appimage.ctl(no subcommand) no longer builds and now exits with an error asking for oneBreaking: removed the
appimagectlconsole script -python -m appimage.ctlalways runs the interpreter you meant,appimagectlrisked silently resolving to whichever install happened to be first onPATHBreaking:
reproducible = truenow requirespythonto be set explicitly in[tool.appimage]-requires-pythonno longer satisfies itBreaking: the
pythondefault (when neitherpythonnorrequires-pythonis set) is now the interpreter runningappimage.ctl, not a hardcoded3.11
Fixed
Cached Python tarball is now keyed by
python/python_date, so changing either no longer silently reuses a stale, mismatched cacheInstalled Python is now verified to actually match the configured
pythonversion, failing immediately instead of confusingly laterSubprocesses (
pip,compileall,appimagetool) now run with a pinnedPYTHONHASHSEED/locale;reproducible = truealso pins them for appimagectl’s own processPackaging now pins mksquashfs to a single compression thread, removing a source of non-deterministic output
Subprocesses and appimagectl’s own process now also pin
TZ=UTC
3.0.1 - 2026-09-02
Fixed
update-toolsnow actually moves an already-pinnedpython_dateforward instead of re-resolving the same old dateAppDir file/directory permissions are now normalized (group/other write bits cleared) before packaging, alongside mtimes
Packaging now strips filesystem xattrs (
-no-xattrs), so build-host security labels (e.g. SELinux) no longer leak into the AppImagePackaging now disables mksquashfs duplicate-file detection (
-no-duplicates), which could otherwise make the packaged image sensitive to incidental per-build state even from an unchanged AppDir
3.0.0 - 2026-09-02
Added
Default appimagetool source switched to
AppImage/appimagetoolfor deterministicmksquashfsoutputappimagetool_sha256/appimagetool_version,runtime_file/runtime_sha256,python_sha256config keys (and matching CLI options) to verify build dependenciesverify_downloadsconfig key /--verify-downloadsCLI flag to abort the build on unverified downloadsrequire_zsyncmakeconfig key /--require-zsyncmakeCLI flag to abort the build whenzsyncmakeis missingreproducibleconfig key /--reproducibleCLI flag: shortcut requiring all reproducibility pins to be setpylock/require_pylockconfig keys and CLI options: hash-pinned dependency installs viapip install --require-hashesbuild_pylock/require_build_pylockconfig keys and CLI options: hash-pinned build-backend installslockcommand: generates/refreshespylock.tomlandbuild_pylockviapip lock;--uploaded-prior-to PnDcooldown windowinitnow also resolves and pins appimagetool and the runtime filecheck/build now print a reproducibility checklist and dependency-verification summaryupdate_infoauto-detection from[project.urls]Bundled
appimageruntime module is hash-verified against PyPI’s digest by default, independent ofpylockcheck’s reproducibility checklist gains ✓/✗ marks and a ready-count headerenable-reproduciblecommand: pins the toolchain, locks dependencies, verifies with a real build, and only then turns onreproducibleThe build now aborts if the resolved appimagetool looks like the classic, unmaintained AppImageKit build (detected via embedded build-path strings and/or its
--versionbanner) instead of the currentAppImage/appimagetooldefault -appimagetool_sha256alone can’t catch this, since it only proves the same (known non-deterministic) file is used every time, not that it’s the right one. See Classic appimagetool detected for the fix.Console-script shims (
AppDir/python/bin/<entry-point>,pip, etc.) that leaked the build machine’s absolute path are now relocated in place instead of deleted - their shebang rewritten to find the bundled interpreter relative to their own location (the same self-locating trick python-build-standalone’s own bundledpipalready uses), confirmed by hand to still run correctly after moving the whole AppDir. Falls back to the previous delete-outright behavior for anything that doesn’t match a recognized pip/distlib shim format byte-for-byte - deliberately narrow, since a general-purpose version of this exact idea (virtualenv’s old--relocatable) was eventually removed upstream for being unreliable. Both shebang shapes pip/distlib can produce (a short one-line#!<path>, or the two-line#!/bin/sh+execfallback once the path is too long) are always rewritten to the two-line form - a self-locating replacement embeds a$(dirname ...)command substitution, which the kernel’s#!handling never shell-expands, so only the two-line form (which names/bin/shas the real interpreter) actually runs it; an earlier version of this rewrite kept a one-line input as a plain one-line#!"$(dirname ...)/python3", which failed at exec time with “bad interpreter: no such file or directory” even without moving the AppDir at all.check(and, via the same code,build/build-appdir) now cross-referencesverify_downloadsagainst whether appimagetool, the runtime file, and the Python archive actually have a matching*_sha256pin - without downloading or hashing anything. Only an explicit config path or an existing build-cache hit with no pin is flagged; a resolution that would download fresh is never a problem (always auto-verified against the digest GitHub publishes, pin or not), so this can’t false-positive on the common case of a first build with nothing cached yet. Surfaces as an early error, sobuildnow fails before starting AppDir assembly instead of only after a full install;build-appdiris unaffected by the appimagetool/runtime-file half of this (it never touches either), consistent with it needing only the AppDir-side pins.appimage_version/appimage_sha256config keys and CLI options to explicitly pin the bundledappimageruntime module instead of implicitly tracking whicheverappimage.ctlrelease does the build;appimagectl_versionconfig key (no CLI override) warns - or aborts underverify_downloads- when theappimage.ctldoing the build drifts from what’s committedupdate-toolscommand: moves every toolchain pin forward to whatever’s currently available (python_date,appimage_version/appimage_sha256,appimagetool_version/appimagetool_sha256,runtime_sha256,appimagectl_version) in one step, overwriting what’s already configured - unlikeinit, which only fills in what’s missing
Changed
Breaking:
--check/--init/--lockare now subcommands (check/init/lock); building stays the default with no subcommandBreaking: console script renamed
appimage-build→appimagectl; module renamedappimage.build→appimage.ctlBreaking: config table renamed
[tool.appimage.build]→[tool.appimage]Breaking: appimagetool resolution no longer searches
PATH- explicitappimagetoolconfig path, then the build cache, then a download, matching every other resolved external input (the bundled Python, the runtime stub). It was the only one that searchedPATH, and in practice that’s exactly how a stray classic AppImageKit build got silently picked up on a real machine (see the “Classic appimagetool detected” entry below). Setappimagetoolexplicitly if you relied on a PATH-found binary.Installed packages are byte-compiled via
pip install --no-compile+compileall --invalidation-mode unchecked-hashSOURCE_DATE_EPOCHis now applied consistently: AppDir file/directory mtimes are normalized to it, and it’s passed into appimagetool’s own process environment
Fixed
write_config()/initno longer crashes withValueErrorwhen a project has no icon fileappimagetool/runtime-file download URLs now map
armv7ltoarmhfcorrectlypylockgeneration no longer drops theappimageruntime module orpackagesentriesinit/write_config()no longer writes a wrongentry_pointguess intopyproject.tomlinit/write_config()now resolves and writespython_dateandpython_sha256_compile_pycnow forces recompilation, fixing a reproducibility break from stale.pyctimestampsbuild_pylockis now applied viapip install --build-constraintinstead of--no-build-isolation, so the build backend no longer stays installed in the shipped AppImageThe build machine’s own absolute path no longer leaks into the AppImage (compiled bytecode, stray stdlib
.pyc,direct_url.json, console-script shims)Every
pip install/pip locksubprocess now setsPYTHONNOUSERSITE=1. Without it, pip additionally resolved against the build user’s own~/.local/lib/pythonX.Y/site-packages(PEP 370) - if a requirement happened to already be satisfied there, pip silently skipped installing it into the AppDir at all (“Requirement already satisfied” instead of “Collecting”). The built AppImage then failed at runtime withModuleNotFoundErroron any host where the build user’s home directory didn’t carry the same leftover package, while the build itself reported success - both a correctness bug and a reproducibility break, since the result depended on unpinned build-host state rather than only the pinned inputs.post_install/pre_packagehooks now get the same isolated environment as every install subprocess (PYTHONNOUSERSITE/PYTHONDONTWRITEBYTECODE). The hook mechanism predates this project’s reproducibility work and was never revisited when it landed - anything a hook does through the bundled interpreter (its documented purpose: editing installed packages between build steps) was exposed to the same PEP 370 leak above.require_zsyncmake/theupdate_info“no.zsyncfile will be generated” warning no longer check whetherzsyncmakeis on the build host’sPATH- checked instead after packaging, against whether appimagetool actually produced the.zsyncfile. The old check answered the wrong question: appimagetool bundles its ownzsyncmakeand itsAppRunputs its ownusr/binfirst onPATH, so.zsyncgeneration was already host-independent - the check just wasn’t, giving a different (and sometimes wrongly negative, aborting underrequire_zsyncmake/--reproducible) answer depending on what happened to be installed on whichever machine ran the build. See Zsync and the build host PATH.The
pylock.tomldependency install no longer passes--no-deps. Since pip’s hash-checking mode (already triggered automatically by any--hashin the file) rejects a resolved candidate with no matching hash, leaving normal dependency resolution on means an incomplete lock - a stalepylock.tomlnot regenerated after apyproject.tomlchange, or alockbug - now aborts the build loudly, instead of silently installing an AppDir missing a transitive dependency that only surfaced asModuleNotFoundErrorwhen the built AppImage was actually run.appimagectlno longer crashes with a raw Python traceback on a failed install/lock/hook/packaging subprocess, or on a malformedpyproject.toml- both now report a cleanError: ...line and a non-zero exit code, same as every other foreseeable failure.An explicitly configured
icon/desktoppath that doesn’t exist now raises a clean, purpose-built error (matching the existingappruncheck) instead of a rawOSErrorsurfacing deep inside asset copying.A build directory containing a space no longer silently drops the entry point’s (and the bundled
appimageruntime module’s) console-script shim from the AppDir.pip/distlib double-quotes an embedded interpreter path whenever it contains a space before writing the shebang; the relocation rewrite only recognized the unquoted form, so the shim fell through to the delete fallback instead of being relocated. Verified end-to-end: a real build at a project path containing a space now produces byte-identical output to the same build at a plain path.A project path containing a non-ASCII character no longer makes packaging fail outright. The pinned
AppImage/appimagetoolbuild’s own CLI option parser fails to decode a--runtime-filevalue containing any non-ASCII byte, independent of locale - confirmed by isolating the failure outside appimagectl entirely. The runtime file is now staged into a guaranteed-ASCII temp path before invoking appimagetool, sidestepping the bug; the staged copy’s content - and the packaged output - is unaffected.
2.0.1 - 2026-07-25
Fixed
appimage.buildnow always installs theappimageruntime module into the bundledsite-packages, pinned to the currently running build version, instead of relying on the packaged project declaringappimageas a dependency itself. Previously, a built AppImage would fail at startup withNo module named appimageunlessappimagewas explicitly listed in[project.dependencies], which none of the documented examples did.
2.0.0 - 2026-05-10
Added
Bundled default icon (AppImage box + Python logo) used as fallback when no project icon is found - build no longer fails without an icon
Icon is always copied into the AppDir as
{app}.{ext}, matching theIcon={app}entry in the.desktopfileappimagetoolconfig key and--appimagetool PATHCLI option: use a local binary instead of downloading; resolution order is config/CLI →PATH→ build cache → downloadpython_archiveconfig key and--python-archive PATHCLI option: use a local python-build-standalone tarball; resolution order is config/CLI → build cache → downloadexamples/myapp/: minimal example project demonstrating zero-configuration usage
Changed
README: restructured with visual header, badges, RTD button; simplified to highlight unique features
License changed from GPL v3 to Apache 2.0
Project metadata: corrected description, homepage, documentation URL and PyPI classifiers
AppRun and
.desktoptemplates moved from inline strings toappimage/build/templates/and loaded viaimportlib.resourcesgenerate_icon.pydeveloper script moved fromappimage/assets/toscripts/(not part of the installed package)
Documentation
Added Sphinx documentation hosted on Read the Docs (sphinx-rtd-theme)
Covers Quick Start, Configuration, CLI reference, Runtime, Internals, and Changelog
CLI reference completed with missing
--app,--entry-point,--extras,--appimagetool,--python-archiveoptionsAdded
examples.mdpage with minimal project walkthrough and offline/CI build instructionsAdded
internals.mdpage explaining how AppImages are built
1.2.0 - 2026-05-08
Added
--python-list-entry-points: lists all available console script entry points (name = module:function) and exits--python-appimage-debug: prints startup debug information to stderr (venv detection, symlink traversal, entry point resolution, interpreter invocation)Virtual environment creation now uses the native
python -m venvinterface via--python-interpreter -m venv ENV_DIR [options]All standard
python -m venvoptions are now supported:--system-site-packages,--clear,--upgrade,--prompt,--without-scm-ignore-filesPython 3.13+:
scm_ignore_filesis now passed toEnvBuilderso.gitignoregeneration can be controlled via--without-scm-ignore-files_activate_venvnow setsVIRTUAL_ENV,sys.prefix,sys.exec_prefix, andsysconfigbase/platbase so activated environments are fully recognised by tooling
Changed
Extracted symlink traversal loop from
setup_virtualenvinto_find_venv_dir_from_symlinkto reduce method complexityRemoved duplicate venv-parsing logic in
parse_python_args; now delegates toparse_venv_commandReplaced monkey-patching of
EnvBuilder.setup_pythonwith a proper_AppImageEnvBuildersubclassBlack formatting check now runs only once (Python 3.11) in CI instead of once per matrix version
CI lint step now uses
hatch run +py=<version> lint:checkto avoid running the full matrix per jobREADME build script now resolves the Python download URL dynamically from the GitHub API using only
PYTHON_MINOR(e.g.3.11), eliminating the need to manually track patch versions and release dates
Fixed
Exception handling in
parse_venv_commandis now narrowed:ValueErroris caught only for the.index()call, preventing unrelated errors from being silently swallowedSymlink depth limit in
setup_virtualenvnow emits a warning to stderr when exceeded instead of failing silentlyFixed infinite loop in
setup_virtualenvwhen following circular symlinks (depth limit: 20 hops)Fixed symlink traversal bug in
setup_virtualenvwherePath.resolve()prevented the loop from ever executingFixed incorrect Python path in module docstring (
opt/python3.11/bin/python3.11→python/bin/python3)Removed false reference to
appimage.iniinAppStarter.__init__docstringFixed
mkdir -p buildin README build script example -build/AppDirwas never created, causingtarto fail
Removed
Removed
--python-venvCLI option; use--python-interpreter -m venv ENV_DIRinstead
1.1.1 - 2026-05-04
Fixed
Fixed
Path.readlink()false positive (assignment-from-no-return) reported by pylint under Python 3.14
Changed
Added Python version matrix to hatch
testandlintenvironments (3.11–3.14)Migrated CI test step from
hatch run test:runtohatch test
1.1.0 - 2026-05-04
Changed
Replaced python-appimage support with astral.sh as the standard AppImage base
Migrated version management from
bumpversiontobump-my-version(config inpyproject.toml)Updated GitHub Actions to current versions (
actions/checkout@v4,actions/setup-python@v5)Migrated PyPI publishing to Trusted Publisher (OIDC), removing the need for a
PYPI_PASSWORDsecret
Removed
Removed support for python-appimage by niess
1.0.0 - 2024-05-27
Added
added compatibility for python-appimage
0.0.0 - 2023-11-01
Initial release on pypi.org