├── .github ├── dependabot.yml └── workflows │ ├── ci.yml │ └── pre-commit-update.yml ├── .gitignore ├── .pre-commit-config.yaml ├── CONTRIBUTING.md ├── LICENSE ├── README.rst ├── cookiecutter.json └── {{ cookiecutter.format }} ├── Dockerfile ├── briefcase.toml └── {{ cookiecutter.formal_name }}.AppDir ├── usr ├── app │ └── README ├── app_packages │ └── README ├── bin │ └── {{ cookiecutter.bundle }}.{{ cookiecutter.app_name }} └── share │ └── icons │ └── hicolor │ ├── 128x128 │ └── apps │ │ └── {{ cookiecutter.bundle_identifier }}.png │ ├── 16x16 │ └── apps │ │ └── {{ cookiecutter.bundle_identifier }}.png │ ├── 256x256 │ └── apps │ │ └── {{ cookiecutter.bundle_identifier }}.png │ ├── 32x32 │ └── apps │ │ └── {{ cookiecutter.bundle_identifier }}.png │ ├── 512x512 │ └── apps │ │ └── {{ cookiecutter.bundle_identifier }}.png │ └── 64x64 │ └── apps │ └── {{ cookiecutter.bundle_identifier }}.png └── {{ cookiecutter.bundle_identifier }}.desktop /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | 2 | version: 2 3 | updates: 4 | - package-ecosystem: "github-actions" 5 | directory: "/" 6 | schedule: 7 | # Check for updates on Sunday, 8PM UTC 8 | interval: "weekly" 9 | day: "sunday" 10 | time: "20:00" 11 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | pull_request: 4 | push: 5 | branches: 6 | - main 7 | workflow_call: 8 | 9 | # Cancel active CI runs for a PR before starting another run 10 | concurrency: 11 | group: ${{ github.workflow }}-${{ github.ref }} 12 | cancel-in-progress: true 13 | 14 | defaults: 15 | run: 16 | shell: bash 17 | 18 | env: 19 | FORCE_COLOR: "1" 20 | 21 | jobs: 22 | pre-commit: 23 | name: Pre-commit checks 24 | uses: beeware/.github/.github/workflows/pre-commit-run.yml@main 25 | with: 26 | pre-commit-source: pre-commit 27 | 28 | verify-apps: 29 | name: Build apps 30 | needs: pre-commit 31 | uses: beeware/.github/.github/workflows/app-build-verify.yml@main 32 | with: 33 | python-version: ${{ matrix.python-version }} 34 | runner-os: ubuntu-latest 35 | framework: ${{ matrix.framework }} 36 | target-platform: linux 37 | target-format: AppImage 38 | strategy: 39 | fail-fast: false 40 | matrix: 41 | python-version: [ "3.9", "3.10", "3.11", "3.12", "3.13" ] 42 | framework: [ "toga", "pyside6", "pygame", "console" ] 43 | -------------------------------------------------------------------------------- /.github/workflows/pre-commit-update.yml: -------------------------------------------------------------------------------- 1 | name: Update pre-commit 2 | 3 | on: 4 | schedule: 5 | - cron: "0 20 * * SUN" # Sunday @ 2000 UTC 6 | workflow_dispatch: 7 | 8 | jobs: 9 | pre-commit-update: 10 | name: Update pre-commit 11 | uses: beeware/.github/.github/workflows/pre-commit-update.yml@main 12 | secrets: inherit 13 | with: 14 | pre-commit-source: pre-commit 15 | create-changenote: false 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .python-version 3 | .vscode 4 | .idea 5 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pre-commit/pre-commit-hooks 3 | rev: v5.0.0 4 | hooks: 5 | - id: check-yaml 6 | - id: check-json 7 | - id: check-xml 8 | - id: check-case-conflict 9 | - id: end-of-file-fixer 10 | - id: trailing-whitespace 11 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | BeeWare <3's contributions! 4 | 5 | Please be aware, BeeWare operates under a Code of Conduct. 6 | 7 | See [CONTRIBUTING to BeeWare](https://beeware.org/contributing) for details. 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Russell Keith-Magee. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Briefcase Linux AppImage Template 2 | ================================= 3 | 4 | A `Cookiecutter `__ template for 5 | building Python apps that will run under Linux, packaged as an AppImage. 6 | 7 | Using this template 8 | ------------------- 9 | 10 | The easiest way to use this project is to not use it at all - at least, not 11 | directly. `Briefcase `__ is a tool that 12 | uses this template, rolling it out using data extracted from a 13 | ``pyproject.toml`` configuration file. 14 | 15 | However, if you *do* want use this template directly... 16 | 17 | 1. Install `cookiecutter`_. This is a tool used to bootstrap complex project 18 | templates:: 19 | 20 | $ pip install cookiecutter 21 | 22 | 2. Run ``cookiecutter`` on the template:: 23 | 24 | $ cookiecutter https://github.com/beeware/briefcase-linux-appimage-template 25 | 26 | This will ask you for a number of details of your application, including the 27 | `name` of your application (which should be a valid PyPI identifier), and 28 | the `Formal Name` of your application (the full name you use to describe 29 | your app). The remainder of these instructions will assume a `name` of 30 | ``my-project``, and a formal name of ``My Project``. 31 | 32 | 3. `Obtain a Python Linux support package for x86_64`_, and extract it into 33 | the ``My Project/My Project.AppDir/usr`` directory generated by the 34 | template. This will give you a self-contained Python install. If installed 35 | correctly, there should be a ``My Project/My 36 | Project.AppDir/usr/bin/python3`` binary, as well as some other 37 | Python-related files. 38 | 39 | Alternatively, you can download the `Python-Linux-support`_ project, and 40 | build your own versions of these frameworks. You will need to do this if 41 | you need to build for an architecture other than `x86_64`. 42 | 43 | 4. Add your code to the template, into the ``My Project/My Project.AppDir/usr/app`` 44 | directory. At the very minimum, you need to have an 45 | ``app//__main__.py`` file that defines an entry point that will 46 | start your application. 47 | 48 | If your code has any dependencies, they should be installed into the 49 | ``My Project/My Project.AppDir/usr/app_packages`` directory. 50 | 51 | If you've done this correctly, a project with a formal name of ``My Project``, 52 | with an app name of ``my-project`` should have a directory structure that 53 | looks something like:: 54 | 55 | My Project/ 56 | My Project.AppDir/ 57 | usr/ 58 | app/ 59 | my_project/ 60 | __init__.py 61 | __main__.py 62 | app.py 63 | app_packages/ 64 | ... 65 | bin/ 66 | python3 67 | ... 68 | lib/ 69 | ... 70 | share/ 71 | ... 72 | com.example.my-project.desktop 73 | briefcase.toml 74 | 75 | This directory can then be compiled into an AppImage using `linuxdeploy`_. 76 | Download the `linuxdeploy AppImage`_, and make the binary executable:: 77 | 78 | $ chmod +x linuxdeploy-x86_64.AppImage 79 | 80 | Then compile your AppDir directory (substituting your release version number):: 81 | 82 | $ VERSION=1.2.3 ./linuxdeploy-x86_64.AppImage --appdir=My\ Project/My\ Project.AppDir -o appimage -d My\ Project/My\ Project.AppDir/com.example.my-project.desktop 83 | 84 | This will produce ``My Project-1.2.3-x86_64.AppImage``. This image can given 85 | to any other Linux user, and should run without installing any other 86 | dependencies. 87 | 88 | Next steps 89 | ---------- 90 | 91 | Of course, running Python code isn't very interesting by itself. 92 | 93 | To do something interesting, you'll need to work with the native system 94 | libraries to draw widgets and respond to user input. The `GTK+`_ GUI library 95 | provides Python bindings that you can use to build a user interface. 96 | Alternatively, you could use a cross-platform widget toolkit that supports 97 | Windows (such as `Toga`_) to provide a GUI for your application. 98 | 99 | If you have any external library dependencies (like Toga, or anything other 100 | third-party library), you should install the library code into the 101 | ``app_packages`` directory. This directory is the same as a ``site_packages`` 102 | directory on a desktop Python install. 103 | 104 | .. _cookiecutter: https://github.com/cookiecutter/cookiecutter 105 | .. _linuxdeploy: https://github.com/linuxdeploy/linuxdeploy 106 | .. _linuxdeploy AppImage: https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage 107 | .. _Obtain a Python Linux support package for x86_64: https://github.com/beeware/Python-Linux-support 108 | .. _Toga: https://beeware.org/project/projects/libraries/toga 109 | .. _GTK+: https://python-gtk-3-tutorial.readthedocs.io/ 110 | -------------------------------------------------------------------------------- /cookiecutter.json: -------------------------------------------------------------------------------- 1 | { 2 | "format": "appimage", 3 | "formal_name": "App Name", 4 | "app_name": "{{ cookiecutter.formal_name|lower|replace(' ', '-') }}", 5 | "module_name": "{{ cookiecutter.app_name|replace('-', '_') }}", 6 | "bundle": "com.example", 7 | "bundle_identifier": "{{ cookiecutter.bundle }}.{{ cookiecutter.app_name|replace('_', '-') }}", 8 | "url": "https://example.com", 9 | "description": "Short description of app", 10 | "python_version": "3.X.0", 11 | "manylinux_image": "", 12 | "vendor_base": "debian", 13 | "use_non_root_user": true, 14 | "dockerfile_extra_content": "", 15 | "_extensions": [ 16 | "briefcase.integrations.cookiecutter.PythonVersionExtension" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /{{ cookiecutter.format }}/Dockerfile: -------------------------------------------------------------------------------- 1 | {% if cookiecutter.manylinux_image -%} 2 | FROM quay.io/pypa/{{ cookiecutter.manylinux_image }} 3 | {%- else %} 4 | # Ubuntu:18.04 based AppImages are for legacy projects 5 | FROM ubuntu:18.04 6 | {%- endif %} 7 | 8 | # Set the working directory 9 | WORKDIR /app 10 | 11 | # Disable pip's warnings 12 | ENV PIP_ROOT_USER_ACTION=ignore \ 13 | PIP_NO_WARN_SCRIPT_LOCATION=0 14 | 15 | {% if cookiecutter.vendor_base == "debian" -%} 16 | # Make sure installation of tzdata is non-interactive 17 | ENV DEBIAN_FRONTEND="noninteractive" 18 | 19 | # Install git and file (for linuxdeploy) 20 | RUN apt-get update -y && \ 21 | apt-get install --no-install-recommends -y git file 22 | {%- else -%} 23 | # Install git and file (for linuxdeploy) 24 | RUN yum install -y git file 25 | {%- endif %} 26 | 27 | {% if cookiecutter.use_non_root_user -%} 28 | # Ensure Docker user UID:GID matches host user UID:GID (beeware/briefcase#403) 29 | # Use --non-unique to avoid problems when the UID:GID of the host user 30 | # collides with entries provided by the Docker container. 31 | ARG HOST_UID 32 | ARG HOST_GID 33 | RUN groupadd --non-unique --gid $HOST_GID briefcase && \ 34 | useradd --non-unique --uid $HOST_UID --gid $HOST_GID brutus --home /home/brutus && \ 35 | mkdir -p /home/brutus && chown brutus:briefcase /home/brutus 36 | {%- endif %} 37 | 38 | # As root, Install system packages required by app 39 | ARG SYSTEM_REQUIRES 40 | {% if cookiecutter.vendor_base == "debian" -%} 41 | RUN apt-get install --no-install-recommends -y build-essential ${SYSTEM_REQUIRES} 42 | {%- else -%} 43 | RUN yum install -y gcc make pkgconfig ${SYSTEM_REQUIRES} 44 | {%- endif %} 45 | 46 | {% if cookiecutter.use_non_root_user -%} 47 | # Use the brutus user for operations in the container 48 | USER brutus 49 | {%- endif %} 50 | 51 | # Configure builds so that clang isn't required, and put the Standalone Python on the build path 52 | ENV CC="gcc -pthread" 53 | ENV PATH="/app/{{ cookiecutter.formal_name }}.AppDir/usr/python/bin:${PATH}" 54 | 55 | # ========== START USER PROVIDED CONTENT ========== 56 | {{ cookiecutter.dockerfile_extra_content }} 57 | -------------------------------------------------------------------------------- /{{ cookiecutter.format }}/briefcase.toml: -------------------------------------------------------------------------------- 1 | # Generated using Python {{ cookiecutter.python_version }} 2 | [briefcase] 3 | target_version = "0.3.20" 4 | 5 | [paths] 6 | app_path = "{{ cookiecutter.formal_name }}.AppDir/usr/app" 7 | app_packages_path = "{{ cookiecutter.formal_name }}.AppDir/usr/app_packages" 8 | support_path = "{{ cookiecutter.formal_name }}.AppDir/usr" 9 | {{ { 10 | "3.9": 'support_revision = "3.9.22+20250409"', 11 | "3.10": 'support_revision = "3.10.17+20250409"', 12 | "3.11": 'support_revision = "3.11.12+20250409"', 13 | "3.12": 'support_revision = "3.12.10+20250409"', 14 | "3.13": 'support_revision = "3.13.3+20250409"', 15 | }.get(cookiecutter.python_version|py_tag, "") }} 16 | # Remove the pieces of the standalone package that we don't need. 17 | cleanup_paths = [ 18 | "{{ cookiecutter.formal_name }}.AppDir/usr/python/bin/2to3*", 19 | "{{ cookiecutter.formal_name }}.AppDir/usr/python/bin/idle3*", 20 | "{{ cookiecutter.formal_name }}.AppDir/usr/python/bin/pydoc*", 21 | "{{ cookiecutter.formal_name }}.AppDir/usr/python/lib/itcl*", 22 | "{{ cookiecutter.formal_name }}.AppDir/usr/python/lib/tcl8*", 23 | "{{ cookiecutter.formal_name }}.AppDir/usr/python/lib/Tix8*", 24 | "{{ cookiecutter.formal_name }}.AppDir/usr/python/lib/tk8*", 25 | ] 26 | 27 | icon.16 = "{{ cookiecutter.formal_name }}.AppDir/usr/share/icons/hicolor/16x16/apps/{{ cookiecutter.bundle_identifier }}.png" 28 | icon.32 = "{{ cookiecutter.formal_name }}.AppDir/usr/share/icons/hicolor/32x32/apps/{{ cookiecutter.bundle_identifier }}.png" 29 | icon.64 = "{{ cookiecutter.formal_name }}.AppDir/usr/share/icons/hicolor/64x64/apps/{{ cookiecutter.bundle_identifier }}.png" 30 | icon.128 = "{{ cookiecutter.formal_name }}.AppDir/usr/share/icons/hicolor/128x128/apps/{{ cookiecutter.bundle_identifier }}.png" 31 | icon.256 = "{{ cookiecutter.formal_name }}.AppDir/usr/share/icons/hicolor/256x256/apps/{{ cookiecutter.bundle_identifier }}.png" 32 | icon.512 = "{{ cookiecutter.formal_name }}.AppDir/usr/share/icons/hicolor/512x512/apps/{{ cookiecutter.bundle_identifier }}.png" 33 | # icon.scalable = "{{ cookiecutter.formal_name }}.AppDir/usr/share/icons/hicolor/scalable/apps/{{ cookiecutter.bundle_identifier }}.png" 34 | -------------------------------------------------------------------------------- /{{ cookiecutter.format }}/{{ cookiecutter.formal_name }}.AppDir/usr/app/README: -------------------------------------------------------------------------------- 1 | Your application code should be placed in this directory. 2 | 3 | The native code will be looking for a {{ cookiecutter.module_name }}/__main__.py file as the entry point. 4 | -------------------------------------------------------------------------------- /{{ cookiecutter.format }}/{{ cookiecutter.formal_name }}.AppDir/usr/app_packages/README: -------------------------------------------------------------------------------- 1 | This directory exists so that 3rd party packages can be installed here. 2 | -------------------------------------------------------------------------------- /{{ cookiecutter.format }}/{{ cookiecutter.formal_name }}.AppDir/usr/bin/{{ cookiecutter.bundle }}.{{ cookiecutter.app_name }}: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | export APPDIR=$(dirname "$0") 3 | # echo "APPDIR=${APPDIR}" 4 | export PYTHONPATH=${APPDIR}/usr/app:${APPDIR}/usr/app_packages 5 | # echo "PYTHONPATH=${PYTHONPATH}" 6 | # echo "PATH=${PATH}" 7 | if [[ -z "${BRIEFCASE_MAIN_MODULE}" ]]; then 8 | BRIEFCASE_MAIN_MODULE="{{ cookiecutter.module_name }}" 9 | fi 10 | # echo "BRIEFCASE_MAIN_MODULE=${BRIEFCASE_MAIN_MODULE}" 11 | "${APPDIR}/usr/python/bin/python3" -u -s -X utf8 -c "import runpy, sys; sys.path.pop(0); runpy.run_module('${BRIEFCASE_MAIN_MODULE}', run_name='__main__', alter_sys=True)" "$@" 12 | -------------------------------------------------------------------------------- /{{ cookiecutter.format }}/{{ cookiecutter.formal_name }}.AppDir/usr/share/icons/hicolor/128x128/apps/{{ cookiecutter.bundle_identifier }}.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beeware/briefcase-linux-appimage-template/f269cdef265cd92649d873ca4245c786357c8a9f/{{ cookiecutter.format }}/{{ cookiecutter.formal_name }}.AppDir/usr/share/icons/hicolor/128x128/apps/{{ cookiecutter.bundle_identifier }}.png -------------------------------------------------------------------------------- /{{ cookiecutter.format }}/{{ cookiecutter.formal_name }}.AppDir/usr/share/icons/hicolor/16x16/apps/{{ cookiecutter.bundle_identifier }}.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beeware/briefcase-linux-appimage-template/f269cdef265cd92649d873ca4245c786357c8a9f/{{ cookiecutter.format }}/{{ cookiecutter.formal_name }}.AppDir/usr/share/icons/hicolor/16x16/apps/{{ cookiecutter.bundle_identifier }}.png -------------------------------------------------------------------------------- /{{ cookiecutter.format }}/{{ cookiecutter.formal_name }}.AppDir/usr/share/icons/hicolor/256x256/apps/{{ cookiecutter.bundle_identifier }}.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beeware/briefcase-linux-appimage-template/f269cdef265cd92649d873ca4245c786357c8a9f/{{ cookiecutter.format }}/{{ cookiecutter.formal_name }}.AppDir/usr/share/icons/hicolor/256x256/apps/{{ cookiecutter.bundle_identifier }}.png -------------------------------------------------------------------------------- /{{ cookiecutter.format }}/{{ cookiecutter.formal_name }}.AppDir/usr/share/icons/hicolor/32x32/apps/{{ cookiecutter.bundle_identifier }}.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beeware/briefcase-linux-appimage-template/f269cdef265cd92649d873ca4245c786357c8a9f/{{ cookiecutter.format }}/{{ cookiecutter.formal_name }}.AppDir/usr/share/icons/hicolor/32x32/apps/{{ cookiecutter.bundle_identifier }}.png -------------------------------------------------------------------------------- /{{ cookiecutter.format }}/{{ cookiecutter.formal_name }}.AppDir/usr/share/icons/hicolor/512x512/apps/{{ cookiecutter.bundle_identifier }}.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beeware/briefcase-linux-appimage-template/f269cdef265cd92649d873ca4245c786357c8a9f/{{ cookiecutter.format }}/{{ cookiecutter.formal_name }}.AppDir/usr/share/icons/hicolor/512x512/apps/{{ cookiecutter.bundle_identifier }}.png -------------------------------------------------------------------------------- /{{ cookiecutter.format }}/{{ cookiecutter.formal_name }}.AppDir/usr/share/icons/hicolor/64x64/apps/{{ cookiecutter.bundle_identifier }}.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beeware/briefcase-linux-appimage-template/f269cdef265cd92649d873ca4245c786357c8a9f/{{ cookiecutter.format }}/{{ cookiecutter.formal_name }}.AppDir/usr/share/icons/hicolor/64x64/apps/{{ cookiecutter.bundle_identifier }}.png -------------------------------------------------------------------------------- /{{ cookiecutter.format }}/{{ cookiecutter.formal_name }}.AppDir/{{ cookiecutter.bundle_identifier }}.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Type=Application 3 | Name={{ cookiecutter.formal_name }} 4 | Exec={{ cookiecutter.bundle_identifier }} %F 5 | Icon={{ cookiecutter.bundle_identifier }} 6 | Categories=Utility; 7 | Comment={{ cookiecutter.description }} 8 | StartupWMClass={{ cookiecutter.formal_name }} 9 | --------------------------------------------------------------------------------