├── .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 |
--------------------------------------------------------------------------------