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

Remove use of platform-site for other-arch macOS wheel installs. #2163

Merged
merged 6 commits into from
Feb 18, 2025
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
1 change: 1 addition & 0 deletions changes/2163.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
The iOS log filter was improved to hide an ignorable message about ``getpwuid_r``.
1 change: 1 addition & 0 deletions changes/2163.removal.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Briefcase can no longer install pure Python macOS packages from a source archive (i.e., a ``.tar.gz`` file published on PyPI). If a package is pure Python, it *must* be provided as a ``py3-none-any`` wheel. Briefcase's `macOS platform documentation <https://briefcase.readthedocs.io/en/latest/reference/platforms/macOS/index.html>`__ contains details on how to provide a ``py3-none-any`` wheel when PyPI does not provide one.
26 changes: 26 additions & 0 deletions docs/reference/platforms/macOS/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,29 @@ signed app bundle.

app
xcode

Platform quirks
===============

Requirements cannot be provided as source tarballs
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Briefcase *cannot* install packages published as source tarballs into a macOS app, even
if the package is a pure Python package that would produce a ``py3-none-any`` wheel.
This is an inherent limitation in the use of source tarballs as a distribution format.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's not really true; it's a limitation in Briefcase.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll stand by this statement. It's not possible to tell purely from a tarball whether a package will produce a binary or non-binary wheel, or to establish anything about the build environments that are supported by the project. That's an inherent limitation in source tarballs as a distribution format for Python artefacts.


If you need to install a package in a macOS app that is only published as a source
tarball, you'll need to compile that package into a wheel first. If the package is pure
Python, you can generate a ``py3-none-any`` wheel using ``pip wheel <package name>``. If
the project has a binary component, you will need to consult the documentation of the
package to determine how to compile a wheel.

You can then directly add the wheel file to the ``requires`` definition for your app, or
put the wheel in a folder and add:

.. code-block:: TOML

requirement_installer_args = ["--find-links", "<path-to-wheel-folder>"]

to your ``pyproject.toml``. This will instruct Briefcase to search that folder for
compatible wheels during the installation process.
43 changes: 36 additions & 7 deletions src/briefcase/platforms/macOS/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,10 +95,31 @@ def _install_app_requirements(
host_app_packages_path = (
self.bundle_path(app) / f"app_packages.{self.tools.host_arch}"
)
# A standard install, except we explicitly reject installs from source
# tarballs with `--only-binary :all:`. This is for two reasons:
#
# 1. Consistency. We need to use `--only-binary :all:` when we do the second
# "other arch" wheel install because of use of the `--platform` argument;
# if we only reject source tarballs from one of the installs, then a
# package that only provides binary wheels for one architecture would
# cause inconsistent results depending on which platform was the host;
# and
#
# 2. Security. Installs from source tarball involve executing arbitrary code
# at time of installation; and it makes the entire development
# environment building the app a vector for introducing vulnerabilities
# into an app. Forcing the use of binary wheels ensures that we can know
# with certainty the provenance of any binary content in the app.
#
# Since Briefcase is a tool designed to produce redistributable binaries,
# we've made the judgement call that the (minor, with known workarounds)
# inconvenience of not being able to use source tarballs is outweighed by
# the need to produce reliable, repeatable binary artefacts.
super()._install_app_requirements(
app,
requires=requires,
app_packages_path=host_app_packages_path,
pip_args=["--only-binary", ":all:"],
)

# Find all the packages with binary components.
Expand All @@ -108,6 +129,18 @@ def _install_app_requirements(
universal_suffix="_universal2",
)

# Determine the min macOS version from the VERSIONS file in the support package.
versions = dict(
[part.strip() for part in line.split(": ", 1)]
for line in (
(self.support_path(app) / "VERSIONS")
.read_text(encoding="UTF-8")
.split("\n")
)
if ": " in line
)
macOS_min_tag = versions.get("Min macOS version", "11.0").replace(".", "_")

# Now install dependencies for the architecture that isn't the host architecture.
other_arch = {
"arm64": "x86_64",
Expand All @@ -131,6 +164,8 @@ def _install_app_requirements(
app_packages_path=other_app_packages_path,
pip_args=[
"--no-deps",
"--platform",
f"macosx_{macOS_min_tag}_{other_arch}",
"--only-binary",
":all:",
]
Expand All @@ -147,13 +182,6 @@ def _install_app_requirements(

in the macOS configuration section of your pyproject.toml.
""",
env={
"PYTHONPATH": str(
self.support_path(app)
/ "platform-site"
/ f"macosx.{other_arch}"
)
},
)
else:
self.console.info("All packages are pure Python, or universal.")
Expand Down Expand Up @@ -185,6 +213,7 @@ def _install_app_requirements(
app,
requires=requires,
app_packages_path=app_packages_path,
pip_args=["--only-binary", ":all:"],
)

# Since we're only targeting 1 architecture, we can strip any universal
Expand Down
15 changes: 8 additions & 7 deletions src/briefcase/platforms/macOS/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,16 @@ def macOS_log_clean_filter(line):
included for analysis purposes (i.e., it's Python content, not a system
message). Returns a single ``None`` if the line should be dumped.
"""
if any(
[
if line.startswith(
(
# Log stream outputs the filter when it starts
line.startswith("Filtering the log data using "),
"Filtering the log data using ",
# Log stream outputs barely useful column headers on startup
line.startswith("Timestamp "),
# iOS reports an ignorable error on startup
line.startswith("Error from getpwuid_r:"),
]
"Timestamp ",
# iOS reports ignorable errors on startup
"Error from getpwuid_r:",
"getpwuid_r did not find a match ",
)
):
return None

Expand Down
14 changes: 14 additions & 0 deletions tests/platforms/macOS/app/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,20 @@ def first_app_templated(first_app_config, tmp_path):
"""<?xml?>\n<installer-script></installer-script>""",
)

# Create the support package VERSIONS file
# with a deliberately weird min macOS version
create_file(
tmp_path / "base_path/build/first-app/macos/app/support/VERSIONS",
"\n".join(
[
"Python version: 3.10.15",
"Build: b11",
"Min macOS version: 15.2",
"",
]
),
)

# Select dmg packaging by default
first_app_config.packaging_format = "dmg"

Expand Down
20 changes: 12 additions & 8 deletions tests/platforms/macOS/app/test_create.py
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,8 @@ def test_install_app_packages(
"--upgrade",
"--no-user",
f"--target={bundle_path / ('app_packages.' + host_arch)}",
"--only-binary",
":all:",
"first",
"second==1.2.3",
"third>=3.2.1",
Expand All @@ -395,18 +397,15 @@ def test_install_app_packages(
"--no-user",
f"--target={bundle_path / ('app_packages.' + other_arch)}",
"--no-deps",
"--platform",
f"macosx_15_2_{other_arch}",
"--only-binary",
":all:",
"second==1.2.3",
"third==3.4.5",
],
check=True,
encoding="UTF-8",
env={
"PYTHONPATH": str(
bundle_path / "support/platform-site" / f"macosx.{other_arch}"
)
},
),
]

Expand Down Expand Up @@ -490,6 +489,8 @@ def test_install_app_packages_no_binary(
"--upgrade",
"--no-user",
f"--target={bundle_path / ('app_packages.' + host_arch)}",
"--only-binary",
":all:",
"first",
"second==1.2.3",
"third>=3.2.1",
Expand Down Expand Up @@ -586,6 +587,8 @@ def test_install_app_packages_failure(create_command, first_app_templated, tmp_p
"--upgrade",
"--no-user",
f"--target={bundle_path / 'app_packages.arm64'}",
"--only-binary",
":all:",
"first",
"second==1.2.3",
"third>=3.2.1",
Expand All @@ -609,16 +612,15 @@ def test_install_app_packages_failure(create_command, first_app_templated, tmp_p
"--no-user",
f"--target={bundle_path / 'app_packages.x86_64'}",
"--no-deps",
"--platform",
"macosx_15_2_x86_64",
"--only-binary",
":all:",
"second==1.2.3",
"third==3.4.5",
],
check=True,
encoding="UTF-8",
env={
"PYTHONPATH": str(bundle_path / "support/platform-site/macosx.x86_64")
},
),
]

Expand Down Expand Up @@ -683,6 +685,8 @@ def test_install_app_packages_non_universal(
"--upgrade",
"--no-user",
f"--target={bundle_path / 'First App.app' / 'Contents' / 'Resources' / 'app_packages'}",
"--only-binary",
":all:",
"first",
"second==1.2.3",
"third>=3.2.1",
Expand Down
4 changes: 4 additions & 0 deletions tests/platforms/macOS/test_macOS_log_clean_filter.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@
"Error from getpwuid_r: 0 (Undefined error: 0)",
None,
),
(
"getpwuid_r did not find a match for uid 501",
None,
),
(
'Filtering the log data using "senderImagePath ENDSWITH "/Toga Test!" '
'OR (processImagePath ENDSWITH "/Toga Test!" '
Expand Down
Loading