diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 4fe2e26..95a6b52 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -38,6 +38,10 @@ jobs:
- name: Install dependencies
run: uv sync --group deployment
+ ### CREATE LIST OF THIRD-PARTY LICENCES
+ - name: Create list of third-party licences
+ run: uv run python packaging/licenses/third_party_licenses.py
+
### INSTALL DEPENDENCY WALKER FOR NUITKA ON WINDOWS
- name: Download Dependency Walker for Nuitka
if: runner.os == 'Windows'
@@ -53,6 +57,9 @@ jobs:
- name: Build app
run: uv run pyside6-deploy --mode standalone --name groupselect2 app/main.py
+ - name: Copy resources
+ run: python -c "import shutil; shutil.copytree('app/resources', 'app/groupselect2.dist/resources')"
+
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
@@ -87,6 +94,10 @@ jobs:
Copy-Item -Recurse "app\groupselect2.dist\*" "$pkg\"
Copy-Item "packaging\windows\AppxManifest.xml" "$pkg\"
Copy-Item -Recurse "packaging\windows\assets" "$pkg\assets"
+ Copy-Item "packaging\licenses\PYTHON_LICENSE.txt" "$pkg\"
+ Copy-Item "THIRD_PARTY_LICENSES.txt" "$pkg\"
+ Copy-Item "packaging\licenses\PYSIDE6_LICENSES.txt" "$pkg\"
+ Copy-Item "packaging\licenses\QT_LIBRARY_REPLACEMENT.txt" "$pkg\"
- name: Build MSIX with makeappx
if: runner.os == 'Windows'
diff --git a/app/main.py b/app/main.py
index bf2b4e9..2538b04 100644
--- a/app/main.py
+++ b/app/main.py
@@ -1,4 +1,7 @@
#!/usr/bin/env python3
+from pathlib import Path
+import sys
+
from base_app.AppContext import AppContext
from GSProject import GSProject
@@ -6,6 +9,12 @@
from GSModelManager import GSModelManager
+if getattr(sys, "frozen", False):
+ RESOURCES_PATH: Path = Path(sys.executable).parent / "resources"
+else:
+ RESOURCES_PATH: Path = Path(__file__).parent / "resources"
+
+
def main():
app_ctx = AppContext(
app_name='GroupSelect',
@@ -14,6 +23,7 @@ def main():
main_window_cls=GSMainWindow,
model_manager_cls=GSModelManager,
project_cls=GSProject,
+ about_html_template=(RESOURCES_PATH / "about.html").read_text(encoding="utf-8")
)
app_ctx.launch()
diff --git a/app/resources/about.html b/app/resources/about.html
new file mode 100644
index 0000000..92dc066
--- /dev/null
+++ b/app/resources/about.html
@@ -0,0 +1,30 @@
+
{app_name} App by Sortition Foundation
+Version: {app_version}
+
+ {app_name} is free and open source software, licensed under the
+ GNU General Public License v3.
+ Source code is available at
+ github.com/sortitionfoundation/groupselect-app.
+
+This software was created by Philipp C. Verpoort while an Associate at the
+ Sortition Foundation. The initial funding for the development was provided by the Involve Foundation.
+
+
+ This application is built with
+ PySide6 and Qt6, used under the
+ GNU Lesser General Public License v3 (LGPLv3).
+ Third-party components used in Qt for Python are listed at
+ doc.qt.io/qtforpython-6/licenses.html.
+ Qt library files included with this application may be replaced with
+ compatible modified versions; see QT_LIBRARY_REPLACEMENT.txt for details.
+
+
+ This application is built with
+ Python 3, used under the
+ Python Software Foundation License v2.
+
+
+ Third-party Python package licence notices are listed in
+ THIRD_PARTY_LICENSES.txt and PYSIDE6_LICENSES.txt,
+ included with this application.
+
diff --git a/packaging/licenses/PYSIDE6_LICENSES.txt b/packaging/licenses/PYSIDE6_LICENSES.txt
new file mode 100644
index 0000000..0a28b63
--- /dev/null
+++ b/packaging/licenses/PYSIDE6_LICENSES.txt
@@ -0,0 +1,178 @@
+PySide6 and Qt6
+===============
+PySide6 is the official Python binding for Qt6, used under the
+GNU Lesser General Public License version 3 (LGPLv3).
+
+Copyright (C) The Qt Company Ltd.
+
+The Qt source code is available at https://code.qt.io
+Third-party components used in Qt for Python are listed at
+https://doc.qt.io/qtforpython-6/licenses.html
+
+---------------------- LGPLv3 ----------------------
+
+ GNU LESSER GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+
+ This version of the GNU Lesser General Public License incorporates
+the terms and conditions of version 3 of the GNU General Public
+License, supplemented by the additional permissions listed below.
+
+ 0. Additional Definitions.
+
+ As used herein, "this License" refers to version 3 of the GNU Lesser
+General Public License, and the "GNU GPL" refers to version 3 of the GNU
+General Public License.
+
+ "The Library" refers to a covered work governed by this License,
+other than an Application or a Combined Work as defined below.
+
+ An "Application" is any work that makes use of an interface provided
+by the Library, but which is not otherwise based on the Library.
+Defining a subclass of a class defined by the Library is deemed a mode
+of using an interface provided by the Library.
+
+ A "Combined Work" is a work produced by combining or linking an
+Application with the Library. The particular version of the Library
+with which the Combined Work was made is also called the "Linked
+Version".
+
+ The "Minimal Corresponding Source" for a Combined Work means the
+Corresponding Source for the Combined Work, excluding any source code
+for portions of the Combined Work that, considered in isolation, are
+based on the Application, and not on the Linked Version.
+
+ The "Corresponding Application Code" for a Combined Work means the
+object code and/or source code for the Application, including any data
+and utility programs needed for reproducing the Combined Work from the
+Application, but excluding the System Libraries of the Combined Work.
+
+ 1. Exception to Section 3 of the GNU GPL.
+
+ You may convey a covered work under sections 3 and 4 of this License
+without being bound by section 3 of the GNU GPL.
+
+ 2. Conveying Modified Versions.
+
+ If you modify a copy of the Library, and, in your modifications, a
+facility refers to a function or data to be supplied by an Application
+that uses the facility (other than as an argument passed when the
+facility is invoked), then you may convey a copy of the modified
+version:
+
+ a) under this License, provided that you make a good faith effort to
+ ensure that, in the event an Application does not supply the
+ function or data, the facility still operates, and performs
+ whatever part of its purpose remains meaningful, or
+
+ b) under the GNU GPL, with none of the additional permissions of
+ this License applicable to that copy.
+
+ 3. Object Code Incorporating Material from Library Header Files.
+
+ The object code form of an Application may incorporate material from
+a header file that is part of the Library. You may convey such object
+code under terms of your choice, provided that, if the incorporated
+material is not limited to numerical parameters, data structure
+layouts and accessors, or small macros, inline functions and templates
+(ten or fewer lines in length), you do both of the following:
+
+ a) Give prominent notice with each copy of the object code that the
+ Library is used in it and that the Library and its use are
+ covered by this License.
+
+ b) Accompany the object code with a copy of the GNU GPL and this license
+ document.
+
+ 4. Combined Works.
+
+ You may convey a Combined Work under terms of your choice that,
+taken together, effectively do not restrict modification of the
+portions of the Library contained in the Combined Work and reverse
+engineering for debugging such modifications, if you also do each of
+the following:
+
+ a) Give prominent notice with each copy of the Combined Work that
+ the Library is used in it and that the Library and its use are
+ covered by this License.
+
+ b) Accompany the Combined Work with a copy of the GNU GPL and this license
+ document.
+
+ c) For a Combined Work that displays copyright notices during
+ execution, include the copyright notice for the Library among
+ these notices, as well as a reference directing the user to the
+ copies of the GNU GPL and this license document.
+
+ d) Do one of the following:
+
+ 0) Convey the Minimal Corresponding Source under the terms of this
+ License, and the Corresponding Application Code in a form
+ suitable for, and under terms that permit, the user to
+ recombine or relink the Application with a modified version of
+ the Linked Version to produce a modified Combined Work, in the
+ manner specified by section 6 of the GNU GPL for conveying
+ Corresponding Source.
+
+ 1) Use a suitable shared library mechanism for linking with the
+ Library. A suitable mechanism is one that (a) uses at run time
+ a copy of the Library already present on the user's computer
+ system, and (b) will operate properly with a modified version
+ of the Library that is interface-compatible with the Linked
+ Version.
+
+ e) Provide Installation Information, but only if you would otherwise
+ be required to provide such information under section 6 of the
+ GNU GPL, and only to the extent that such information is
+ necessary to install and execute a modified version of the
+ Combined Work produced by recombining or relinking the
+ Application with a modified version of the Linked Version. (If
+ you use option 4d0, the Installation Information must accompany
+ the Minimal Corresponding Source and Corresponding Application
+ Code. If you use option 4d1, you must provide the Installation
+ Information in the manner specified by section 6 of the GNU GPL
+ for conveying Corresponding Source.)
+
+ 5. Combined Libraries.
+
+ You may place library facilities that are a work based on the
+Library side by side in a single library together with other library
+facilities that are not Applications and are not covered by this
+License, and convey such a combined library under terms of your
+choice, if you do both of the following:
+
+ a) Accompany the combined library with a copy of the same work based
+ on the Library, uncombined with any other library facilities,
+ conveyed under the terms of this License.
+
+ b) Give prominent notice with the combined library that part of it
+ is a work based on the Library, and explaining where to find the
+ accompanying uncombined form of the same work.
+
+ 6. Revised Versions of the GNU Lesser General Public License.
+
+ The Free Software Foundation may publish revised and/or new versions
+of the GNU Lesser General Public License from time to time. Such new
+versions will be similar in spirit to the present version, but may
+differ in detail to address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Library as you received it specifies that a certain numbered version
+of the GNU Lesser General Public License "or any later version"
+applies to it, you have the option of following the terms and
+conditions either of that published version or of any later version
+published by the Free Software Foundation. If the Library as you
+received it does not specify a version number of the GNU Lesser
+General Public License, you may choose any version of the GNU Lesser
+General Public License ever published by the Free Software Foundation.
+
+ If the Library as you received it specifies that a proxy can decide
+whether future versions of the GNU Lesser General Public License shall
+apply, that proxy's public statement of acceptance of any version is
+permanent authorization for you to choose that version for the
+Library.
diff --git a/packaging/licenses/PYTHON_LICENSE.txt b/packaging/licenses/PYTHON_LICENSE.txt
new file mode 100644
index 0000000..e7faf89
--- /dev/null
+++ b/packaging/licenses/PYTHON_LICENSE.txt
@@ -0,0 +1,40 @@
+1. This LICENSE AGREEMENT is between the Python Software Foundation ("PSF"), and
+ the Individual or Organization ("Licensee") accessing and otherwise using this
+ software ("Python") in source or binary form and its associated documentation.
+
+2. Subject to the terms and conditions of this License Agreement, PSF hereby
+ grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce,
+ analyze, test, perform and/or display publicly, prepare derivative works,
+ distribute, and otherwise use Python alone or in any derivative
+ version, provided, however, that PSF's License Agreement and PSF's notice of
+ copyright, i.e., "Copyright © 2001 Python Software Foundation; All Rights
+ Reserved" are retained in Python alone or in any derivative version
+ prepared by Licensee.
+
+3. In the event Licensee prepares a derivative work that is based on or
+ incorporates Python or any part thereof, and wants to make the
+ derivative work available to others as provided herein, then Licensee hereby
+ agrees to include in any such work a brief summary of the changes made to Python.
+
+4. PSF is making Python available to Licensee on an "AS IS" basis.
+ PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED. BY WAY OF
+ EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND DISCLAIMS ANY REPRESENTATION OR
+ WARRANTY OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE
+ USE OF PYTHON WILL NOT INFRINGE ANY THIRD PARTY RIGHTS.
+
+5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
+ FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS A RESULT OF
+ MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, OR ANY DERIVATIVE
+ THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
+
+6. This License Agreement will automatically terminate upon a material breach of
+ its terms and conditions.
+
+7. Nothing in this License Agreement shall be deemed to create any relationship
+ of agency, partnership, or joint venture between PSF and Licensee. This License
+ Agreement does not grant permission to use PSF trademarks or trade name in a
+ trademark sense to endorse or promote products or services of Licensee, or any
+ third party.
+
+8. By copying, installing or otherwise using Python, Licensee agrees
+ to be bound by the terms and conditions of this License Agreement.
\ No newline at end of file
diff --git a/packaging/licenses/QT_LIBRARY_REPLACEMENT.txt b/packaging/licenses/QT_LIBRARY_REPLACEMENT.txt
new file mode 100644
index 0000000..158cdf4
--- /dev/null
+++ b/packaging/licenses/QT_LIBRARY_REPLACEMENT.txt
@@ -0,0 +1,15 @@
+Qt Library Replacement Notice
+==============================
+
+This application uses Qt6 and PySide6, distributed as separate DLL files
+in accordance with the GNU Lesser General Public License version 3 (LGPLv3).
+
+In compliance with the LGPLv3, you are free to replace the Qt6 and PySide6
+library files (qt6*.dll, pyside6.abi3.dll, shiboken6.abi3.dll, and files
+in the PySide6/ subdirectory) with compatible modified versions of your
+own, provided the modified libraries remain interface-compatible with the
+originals.
+
+The Qt source code is available at: https://code.qt.io
+Build instructions for PySide6 are available at:
+https://doc.qt.io/qtforpython-6/gettingstarted/index.html
diff --git a/packaging/licenses/third_party_licenses.py b/packaging/licenses/third_party_licenses.py
new file mode 100644
index 0000000..94c4851
--- /dev/null
+++ b/packaging/licenses/third_party_licenses.py
@@ -0,0 +1,32 @@
+import re
+import subprocess
+
+
+# Get runtime packages from uv export
+export = subprocess.run(
+ ["uv", "export", "--no-dev", "--no-hashes", "--format=requirements-txt"],
+ capture_output=True, text=True, check=True
+)
+
+all_packages = []
+for line in export.stdout.splitlines():
+ line = line.strip()
+ if not line or line.startswith("#"):
+ continue
+ name = re.split(r'[@=><\s]', line)[0]
+ if name:
+ all_packages.append(name)
+
+pyside_packages = [p for p in all_packages if p.lower().startswith("pyside6") or p.lower() == "shiboken6"]
+other_packages = [p for p in all_packages if p not in pyside_packages]
+
+subprocess.run(
+ [
+ "uv", "run", "pip-licenses",
+ "--with-authors", "--with-urls", "--with-license-file", "--no-license-path",
+ "--format=plain-vertical",
+ "--packages", *other_packages,
+ "--output-file=THIRD_PARTY_LICENSES.txt",
+ ],
+ check=True
+)
diff --git a/pyproject.toml b/pyproject.toml
index 192a271..9447055 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -26,6 +26,7 @@ deployment = [
"ordered-set>=4.1.0",
"patchelf>=0.17.2.4; sys_platform == 'linux'",
"pip>=26.0.1",
+ "pip-licenses>=5.5.1",
"setuptools>=81.0.0",
"wheel>=0.46.3",
"zstandard>=0.25.0",
diff --git a/uv.lock b/uv.lock
index a0a5721..8e93b83 100644
--- a/uv.lock
+++ b/uv.lock
@@ -5,7 +5,7 @@ requires-python = "==3.11.*"
[[package]]
name = "base-app"
version = "0.1.0"
-source = { git = "https://github.com/sortitionfoundation/base-app/?tag=v0.1.0#789b8847ea44c1fd99a4f59071b9c36faa3555ec" }
+source = { git = "https://github.com/sortitionfoundation/base-app/?tag=v0.1.0#a43fc880ec8e81f19b8ffa76f9ba1e05888ffe73" }
dependencies = [
{ name = "jsonpickle" },
{ name = "pyside6" },
@@ -54,6 +54,7 @@ deployment = [
{ name = "ordered-set" },
{ name = "patchelf", marker = "sys_platform == 'linux'" },
{ name = "pip" },
+ { name = "pip-licenses" },
{ name = "setuptools" },
{ name = "wheel" },
{ name = "zstandard" },
@@ -73,6 +74,7 @@ deployment = [
{ name = "ordered-set", specifier = ">=4.1.0" },
{ name = "patchelf", marker = "sys_platform == 'linux'", specifier = ">=0.17.2.4" },
{ name = "pip", specifier = ">=26.0.1" },
+ { name = "pip-licenses", specifier = ">=5.5.1" },
{ name = "setuptools", specifier = ">=81.0.0" },
{ name = "wheel", specifier = ">=0.46.3" },
{ name = "zstandard", specifier = ">=0.25.0" },
@@ -198,6 +200,30 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/de/f0/c81e05b613866b76d2d1066490adf1a3dbc4ee9d9c839961c3fc8a6997af/pip-26.0.1-py3-none-any.whl", hash = "sha256:bdb1b08f4274833d62c1aa29e20907365a2ceb950410df15fc9521bad440122b", size = 1787723, upload-time = "2026-02-05T02:20:16.416Z" },
]
+[[package]]
+name = "pip-licenses"
+version = "5.5.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "prettytable" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/44/4c/b4be9024dae3b5b3c0a6c58cc1d4a35fffe51c3adb835350cb7dcd43b5cd/pip_licenses-5.5.1.tar.gz", hash = "sha256:7df370e6e5024a3f7449abf8e4321ef868ba9a795698ad24ab6851f3e7fc65a7", size = 49108, upload-time = "2026-01-27T21:46:41.432Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/a0/a3/0b369cdffef3746157712804f1ded9856c75aa060217ee206f742c74e753/pip_licenses-5.5.1-py3-none-any.whl", hash = "sha256:ed5e229a93760e529cfa7edaec6630b5a2cd3874c1bddb8019e5f18a723fdead", size = 22108, upload-time = "2026-01-27T21:46:39.766Z" },
+]
+
+[[package]]
+name = "prettytable"
+version = "3.17.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "wcwidth" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/79/45/b0847d88d6cfeb4413566738c8bbf1e1995fad3d42515327ff32cc1eb578/prettytable-3.17.0.tar.gz", hash = "sha256:59f2590776527f3c9e8cf9fe7b66dd215837cca96a9c39567414cbc632e8ddb0", size = 67892, upload-time = "2025-11-14T17:33:20.212Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/ee/8c/83087ebc47ab0396ce092363001fa37c17153119ee282700c0713a195853/prettytable-3.17.0-py3-none-any.whl", hash = "sha256:aad69b294ddbe3e1f95ef8886a060ed1666a0b83018bbf56295f6f226c43d287", size = 34433, upload-time = "2025-11-14T17:33:19.093Z" },
+]
+
[[package]]
name = "pyside6"
version = "6.10.2"
@@ -306,6 +332,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl", hash = "sha256:06a47e5700f3081aab02b2e513160914ff0694bce9947d6b76ebd6bf57cfc5d1", size = 348521, upload-time = "2025-12-13T17:45:33.889Z" },
]
+[[package]]
+name = "wcwidth"
+version = "0.6.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/35/a2/8e3becb46433538a38726c948d3399905a4c7cabd0df578ede5dc51f0ec2/wcwidth-0.6.0.tar.gz", hash = "sha256:cdc4e4262d6ef9a1a57e018384cbeb1208d8abbc64176027e2c2455c81313159", size = 159684, upload-time = "2026-02-06T19:19:40.919Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/68/5a/199c59e0a824a3db2b89c5d2dade7ab5f9624dbf6448dc291b46d5ec94d3/wcwidth-0.6.0-py3-none-any.whl", hash = "sha256:1a3a1e510b553315f8e146c54764f4fb6264ffad731b3d78088cdb1478ffbdad", size = 94189, upload-time = "2026-02-06T19:19:39.646Z" },
+]
+
[[package]]
name = "wheel"
version = "0.46.3"