Переглянути джерело

Improve python selection when building apps

Valentin Niess 4 роки тому
батько
коміт
4beacdf473

+ 73 - 4
docs/docs/apps.md

@@ -62,7 +62,7 @@ returns the location of `appimagetool`, if it has been installed. If not, the
 {{ end(".capsule") }}
 
 
-## Manylinux Python AppImage
+## Manylinux Python AppImages
 
 AppImages of your local `python` are unlikely to be portable, except if you run
 an ancient Linux distribution. Indeed, a core component preventing portability
@@ -99,9 +99,78 @@ install found in the `manylinux2014_x86_64` Docker image.
 
 ## Simple packaging
 
-The recipe folder contains
-the app metadata, a Python requirements file and an entry point script. Examples
-of recipes can be found on GitHub in the [applications][APPLICATIONS] folder.
+The `python-appimage` utility can also be used in order to build simple
+applications, that can be `pip` installed. The syntax is
+
+```bash
+python-appimage build app -p 3.10 /path/to/recipe/folder
+```
+
+in order to build a Python 3.10 based application from a recipe folder.
+Examples of recipes can be found on GitHub in the [applications][APPLICATIONS]
+folder.  The recipe folder contains:
+
+- the AppImage metadata (`application.xml` and `application.desktop`),
+- an application icon (e.g. `application.png`),
+- a Python requirements file (`requirements.txt`)
+- an entry point script (`entrypoint.sh`).
+
+Additional information on metadata can be found in the AppImage documentation.
+That is, for [desktop][APPIMAGE_DESKTOP] and [AppStream XML][APPIMAGE_XML]
+files. The `requirements.txt` file allows to specify additional site packages
+to be bundled in the AppImage, using `pip`.
+
+!!! Caution
+    Site packages bundled in the AppImage, as well as their dependencies, must
+    either be pure python packages, or they must be available as portable binary
+    wheels.
+
+    If a **C extension** is bundled from **source**, then it will likely **not**
+    be **portable**, as further discussed in the [Advanced
+    packaging](#advanced-packaging) section.
+
+{{ begin(".capsule") }}
+### Entry point script
+
+{% raw %}
+The entry point script deserves some additional explanations. This script allows
+to customize the startup of your application. A typical `entrypoint.sh` script
+would look like
+
+```bash
+{{ python-executable }} ${APPDIR}/opt/python{{ python-version }}/bin/my_app.py "$@"
+```
+
+where `my_app.py` is the application startup script, installed by `pip`. As can
+be seen from the previous example, the `entrypoint.sh` script recognises some
+particular variables, nested between double curly braces, `{{ }}`. Those
+variables are listed in the table hereafter. In addition, usual [AppImage
+environement variables][APPIMAGE_ENV] can be used as well, if needed. For
+example, `$APPDIR` points to the AppImage mount point at runtime.
+{% endraw %}
+
+{{ begin("#entrypoint-variables") }}
+| variable             | Description                                                   |
+|----------------------|---------------------------------------------------------------|
+| `architecture`       | The AppImage architecture, e.g. `x86_64`.                     |
+| `linux-tag`          | The Manylinux compatibility tag, e.g. `manylinux2014_x86_64`. |
+| `python-executable`  | Path to the AppImage Python runtime.                          |
+| `python-fullversion` | The Python full version string, e.g. `3.10.2`.                |
+| `python-tag`         | The Python compatibility tag, e.g. `cp310-cp310`.             |
+| `python-version`     | The Python short version string, e.g. `3.10`.                 |
+{{ end("#entrypoint-variables") }}
+{{ end(".capsule") }}
+
+{% raw %}
+!!! Note
+    By default, Python AppImages are not isolated from the user space, nor from
+    Python specific environment variables, the like `PYTHONPATH`. Depending on
+    your use case, this can be problematic.
+
+    The runtime isolation level can be changed by adding the `-s` and `-E`
+    options, when invoking the runtime.  For example,
+    `{{ python-executable }} -sE` starts a fully isolated Python instance.
+{% endraw %}
 
 
 ## Advanced packaging

+ 4 - 0
docs/include/references.md

@@ -1,6 +1,10 @@
 {# References used in the documentation #}
 
 [APPIMAGE]: https://appimage.org/
+[APPIMAGE_APPRUN]: https://docs.appimage.org/introduction/software-overview.html#apprun
+[APPIMAGE_DESKTOP]: https://docs.appimage.org/reference/desktop-integration.html#
+[APPIMAGE_ENV]: https://docs.appimage.org/packaging-guide/environment-variables.html
+[APPIMAGE_XML]: https://docs.appimage.org/packaging-guide/optional/appstream.html
 [APPIMAGETOOL]: https://appimage.github.io/appimagetool/
 [APPLICATIONS]: {{ config.repo_url }}tree/master/applications/
 [GITHUB]: {{ config.repo_url }}

+ 15 - 4
python_appimage/commands/build/app.py

@@ -16,6 +16,7 @@ from ...utils.system import system
 from ...utils.template import copy_template, load_template
 from ...utils.tmp import TemporaryDirectory
 from ...utils.url import urlopen, urlretrieve
+from ...utils.version import tonumbers
 
 
 __all__ = ['execute']
@@ -29,6 +30,7 @@ def _unpack_args(args):
 
 
 _tag_pattern = re.compile('python([^-]+)[-]([^.]+)[.]AppImage')
+_linux_pattern = re.compile('manylinux([0-9]+)_' + platform.machine())
 
 def execute(appdir, name=None, python_version=None, linux_tag=None,
             python_tag=None, base_image=None, in_tree_build=False):
@@ -52,7 +54,7 @@ def execute(appdir, name=None, python_version=None, linux_tag=None,
                 continue
             v = tag[6:]
             if python_version is None:
-                if v > version:
+                if tonumbers(v) > tonumbers(version):
                     release, version = entry, v
             elif v == python_version:
                 version = python_version
@@ -66,18 +68,27 @@ def execute(appdir, name=None, python_version=None, linux_tag=None,
 
 
         # Check for a suitable image
+        assets = release['assets']
+
         if linux_tag is None:
-            linux_tag = 'manylinux1_' + platform.machine()
+            plat = None
+            for asset in assets:
+                match = _linux_pattern.search(asset['name'])
+                if match:
+                    tmp = str(match.group(1))
+                    if (plat is None) or (tmp < plat):
+                        plat = tmp
+
+            linux_tag = 'manylinux' + plat + '_' + platform.machine()
 
         if python_tag is None:
             v = ''.join(version.split('.'))
             python_tag = 'cp{0:}-cp{0:}'.format(v)
-            if version < '3.8':
+            if tonumbers(version) < tonumbers('3.8'):
                 python_tag += 'm'
 
         target_tag = '-'.join((python_tag, linux_tag))
 
-        assets = release['assets']
         for asset in assets:
             match = _tag_pattern.search(asset['name'])
             if str(match.group(2)) == target_tag:

+ 7 - 0
python_appimage/utils/version.py

@@ -0,0 +1,7 @@
+__all__ = ['tonumbers']
+
+
+def tonumbers(s):
+    '''Convert a version string to a list of numbers, for comparison
+    '''
+    return [int(v) for v in s.split('.')]