Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Updates to support iOS dynamic binary modules #33

Merged
merged 14 commits into from
Sep 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 15 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,11 @@ jobs:
backend:
- "macOS-Xcode"
- "macOS-app"
- "linux-appimage"
# AppImage builds (a) take over an hour, because the need to compile Numpy and
# Pandas from source, and (b) are only here as a light validation that
# Standalone Python is working. A Flatpak build also validates this, but
# completes in minutes.
# - "linux-appimage"
- "linux-flatpak"
- "windows-VisualStudio"
- "windows-app"
Expand All @@ -48,10 +52,16 @@ jobs:
runs-on: macos-latest
briefcase-target: "macOS app"

- backend: linux-appimage
- backend: linux-system
runs-on: ubuntu-latest
pre-command: "sudo apt-get update -y && sudo apt-get install -y python3-gi python3-gi-cairo gir1.2-gtk-3.0 python3-dev libgirepository1.0-dev libcairo2-dev pkg-config libfuse2"
briefcase-target: "linux appimage"
python-version: "system"
pre-command: "sudo apt-get update -y && sudo apt-get install -y python3-gi python3-gi-cairo gir1.2-gtk-3.0 python3-dev libgirepository1.0-dev libcairo2-dev pkg-config"
briefcase-target: "linux system"

# - backend: linux-appimage
# runs-on: ubuntu-latest
# pre-command: "sudo apt-get update -y && sudo apt-get install -y python3-gi python3-gi-cairo gir1.2-gtk-3.0 python3-dev libgirepository1.0-dev libcairo2-dev pkg-config libfuse2"
# briefcase-target: "linux appimage"

- backend: linux-flatpak
runs-on: ubuntu-latest
Expand Down Expand Up @@ -91,6 +101,7 @@ jobs:

- name: Set up Python
uses: actions/[email protected]
if: matrix.python-version != 'system'
with:
python-version: ${{ matrix.python-version }}

Expand Down
8 changes: 2 additions & 6 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
__pycache__/
.idea
*.dist-info/
macOS
iOS
android
windows
linux
briefcase.*.log
build/
logs/
5 changes: 5 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Python Support Testbed Release Notes

## 1.0.0

* Rolling release.
69 changes: 54 additions & 15 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ author_email = "[email protected]"
[tool.briefcase.app.testbed]
formal_name = "Testbed"
description = "A testbed for the Apple Support packages."
long_description = """A test of common use cases and known problems with bundled apps.

Validates 3rd party module loading, as well as basic app packaging.
"""
icon = "src/testbed/resources/testbed"
sources = ["src/testbed"]
test_sources = ["tests"]
Expand Down Expand Up @@ -44,24 +48,44 @@ requires=[
'pygobject',
]

[tool.briefcase.app.testbed.linux.system.debian]
system_requires = [
# Needed to compile pycairo wheel
"libcairo2-dev",
# Needed to compile PyGObject wheel
"libgirepository1.0-dev",
]

system_runtime_requires = [
# Needed to provide GTK and its GI bindings
"gir1.2-gtk-3.0",
"libgirepository-1.0-1",
# Dependencies that GTK looks for at runtime
"libcanberra-gtk3-module",
]

[tool.briefcase.app.testbed.linux.appimage]
requires=[
'--no-binary', ':all:'
manylinux = "manylinux_2_28"
requires = [
'--no-binary', ':all:',
]

system_requires = [
# Required for GTK/PyGObject tests
'gir1.2-gtk-3.0',
'libgirepository1.0-dev',
# Required for pycairo
'libcairo2-dev',
# Required for Pillow
'libjpeg-dev',
'libpng-dev',
'libtiff-dev',
# Required to install Rust
'curl',
'libssl-dev',
# Needed to compile pycairo wheel
"cairo-gobject-devel",
# Needed to compile PyGObject wheel
"gobject-introspection-devel",
# Needed to provide GTK
"gtk3-devel",
# Dependencies that GTK looks for at runtime, that need to be
# in the build environment to be picked up by linuxdeploy
"libcanberra-gtk3",
"PackageKit-gtk3-module",
"gvfs-client",
# Needed to compile Pillow
"libjpeg-devel",
# Needed to compile Cryptography
"openssl-devel",
]

linuxdeploy_plugins = [
Expand All @@ -71,13 +95,28 @@ linuxdeploy_plugins = [
dockerfile_extra_content = """
# Install Rust (required for cryptography)
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
ENV PATH="/home/brutus/.cargo/bin:${PATH}"
# Set up compilers (required for numpy)
ENV CXX="g++ -pthread"
ENV AR=ar
# Add a symlink for python->python3 (needed for pandas)
# Pandas *requires* that the Python binary be called `python`, not `python3`.
# However, Python-standalone *only* provides `python3`, and at runtime, we
# only need `python3`. We can't (easily) reconfigure the Meson build for Pandas,
# so we put a symlink in `brutus`'s bin path.
RUN mkdir -p /home/brutus/bin
RUN ln -si /app/Testbed.AppDir/usr/python/bin/python3 /home/brutus/bin/python
# Set the path to include all the things we've installed.
ENV PATH="/home/brutus/bin:/home/brutus/.cargo/bin:${PATH}"
"""

# support_package = "../Python-linux-support/dist/Python-3.10-linux-x86_64-support.custom.tar.gz"
# template = "../../templates/briefcase-linux-appimage-template"

[tool.briefcase.app.testbed.linux.flatpak]
flatpak_runtime = "org.gnome.Platform"
flatpak_runtime_version = "44"
flatpak_sdk = "org.gnome.Sdk"

# template = "../../templates/briefcase-linux-flatpak-template"

[tool.briefcase.app.testbed.windows]
Expand Down
1 change: 1 addition & 0 deletions src/testbed/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@

if __name__ == "__main__":
main()
print("Did you forget to invoke with --test?")
9 changes: 1 addition & 8 deletions src/testbed/android.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
######################################################################
# Android App main loop
#
# The main loop itself is a no-op; however we need a PythonAppDelegate
# to satisfy the app stub.
# Android App configuration
#######################################################################
from rubicon.java import JavaClass, JavaInterface

Expand All @@ -22,7 +19,3 @@ def __init__(self, app):
self._impl = app
MainActivity.setPythonApp(self)
print("Python app launched & stored in Android Activity class")


def main_loop():
pass
5 changes: 2 additions & 3 deletions src/testbed/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ def main():
module_path = ".android"
else:
module_path = f".{sys.platform}"
platform_module = importlib.import_module(module_path, "testbed")

# Run the main_loop() for the platform
platform_module.main_loop()
# Import the platform module, so we get any import side effects.
importlib.import_module(module_path, "testbed")
15 changes: 1 addition & 14 deletions src/testbed/darwin.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,3 @@
######################################################################
# macOS App main loop
# No macOS App configuration required
#######################################################################
from rubicon.objc import ObjCClass
from rubicon.objc.runtime import load_library

appkit = load_library("AppKit")

NSApplication = ObjCClass("NSApplication")
NSApplication.declare_class_property("sharedApplication")


def main_loop():
app = NSApplication.sharedApplication
app.setActivationPolicy(0)
app.run()
9 changes: 1 addition & 8 deletions src/testbed/ios.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
######################################################################
# iOS App main loop
#
# The main loop itself is a no-op; however we need a PythonAppDelegate
# to satisfy the app stub.
# iOS App configuration
#######################################################################
from rubicon.objc import ObjCClass

Expand All @@ -11,7 +8,3 @@

class PythonAppDelegate(UIResponder):
pass


def main_loop():
pass
13 changes: 1 addition & 12 deletions src/testbed/linux.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,3 @@
######################################################################
# Linux App main loop
# No Linux App configuration required
#######################################################################
import gi

gi.require_version("Gtk", "3.0")
from gi.repository import Gtk # noqa; E402


def main_loop():
win = Gtk.Window()
win.connect("destroy", Gtk.main_quit)
win.show_all()
Gtk.main()
20 changes: 1 addition & 19 deletions src/testbed/win32.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,3 @@
######################################################################
# Windows App main loop
# No Windows App configuration required
#######################################################################
import clr

clr.AddReference("System.Windows.Forms")

import System.Windows.Forms as WinForms # noqa; E402


class TestBed(WinForms.Form):
def __init__(self):
super().__init__()

def run(self):
WinForms.Application.Run(self)


def main_loop():
form = TestBed()
WinForms.Application.Run(form)
6 changes: 4 additions & 2 deletions tests/test_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,10 +184,12 @@ def test_dbm_dumb():


@pytest.mark.skipif(
hasattr(sys, "getandroidapilevel"), reason="NDBM not available on Android"
hasattr(sys, "getandroidapilevel"),
reason="NDBM not available on Android",
)
@pytest.mark.skipif(
sys.platform == "linux", reason="NDBM not universally available on Linux"
sys.platform == "linux",
reason="NDBM not universally available on Linux",
)
@pytest.mark.skipif(sys.platform == "win32", reason="NDBM not available on Windows")
def test_dbm_ndbm():
Expand Down
1 change: 1 addition & 0 deletions tests/test_linux.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
pytest.skip("Skipping Linux-only tests", allow_module_level=True)


@pytest.mark.skip("GNU DBM not provided by Standalone Python")
def test_dbm_gdbm():
"The GNU DBM module has been compiled and works"
import tempfile
Expand Down
33 changes: 24 additions & 9 deletions tests/test_thirdparty.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,24 @@
###########################################################################
import os
import sys
import warnings
from pathlib import Path

import pytest


def test_module_paths():
"Third party binary modules have meaningful __file__ attributes"
import PIL
from PIL import _imaging

# iOS and Android both play shenanigans with binary locations.
# Make sure the __file__ attribute on the binary module reflects
# its package's location in the file system. The base PIL module is
# pure Python; the binary module for PIL._imaging should *appear*
# to be in the same folder (although in practice, it may not be).
assert Path(_imaging.__file__).parent == Path(PIL.__file__).parent


@pytest.mark.skipif(sys.platform == "win32", reason="cffi not available on windows")
def test_cffi():
"CFFI can be used as an alternative FFI interface"
Expand All @@ -21,7 +34,7 @@ def test_cffi():

def test_cryptography():
"The cryptography module can be used"
# Cryptography is a common binary library that uses cffi and OpenSSL (1.1.1) internally
# Cryptography is a common binary library that uses cffi and OpenSSL internally
from textwrap import dedent

from cryptography import x509
Expand Down Expand Up @@ -119,17 +132,19 @@ def test_numpy():

def test_pandas():
"Pandas DataFrames can be created"
from pandas import DataFrame
from pandas import DataFrame, __version__

# Another high profile package, with a dependency on numpy
df = DataFrame(
[("alpha", 1), ("bravo", 2), ("charlie", 3)], columns=["Letter", "Number"]
)

with warnings.catch_warnings():
# Pandas 1.5 changed the `line_terminator` argument to `lineterminator`
warnings.filterwarnings("ignore", category=FutureWarning)
# Pandas 1.5 changed the API for to_csv()
if tuple(int(v) for v in __version__.split(".")) < (1, 5):
kwargs = dict(line_terminator="\n")
else:
kwargs = dict(lineterminator="\n")

assert (
",Letter,Number\n" "0,alpha,1\n" "1,bravo,2\n" "2,charlie,3\n"
) == df.to_csv(line_terminator="\n")
assert (
",Letter,Number\n" "0,alpha,1\n" "1,bravo,2\n" "2,charlie,3\n"
) == df.to_csv(**kwargs)
5 changes: 5 additions & 0 deletions tests/testbed.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

import pytest

from testbed.app import main as app_main


def run_tests():
project_path = Path(__file__).parent.parent
Expand All @@ -25,4 +27,7 @@ def run_tests():


if __name__ == "__main__":
# Run the app main to stimulate app creation and logging of test conditions.
app_main()
# Run the actual test suite.
run_tests()