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

gh-120522: Apply App Store compliance patch during installation #121947

Merged
merged 4 commits into from
Jul 21, 2024
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
10 changes: 9 additions & 1 deletion Doc/library/urllib.parse.rst
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,19 @@ to an absolute URL given a "base URL."

The module has been designed to match the internet RFC on Relative Uniform
Resource Locators. It supports the following URL schemes: ``file``, ``ftp``,
``gopher``, ``hdl``, ``http``, ``https``, ``imap``, ``mailto``, ``mms``,
``gopher``, ``hdl``, ``http``, ``https``, ``imap``, ``itms-services``, ``mailto``, ``mms``,
``news``, ``nntp``, ``prospero``, ``rsync``, ``rtsp``, ``rtsps``, ``rtspu``,
``sftp``, ``shttp``, ``sip``, ``sips``, ``snews``, ``svn``, ``svn+ssh``,
``telnet``, ``wais``, ``ws``, ``wss``.

.. impl-detail::

The inclusion of the ``itms-services`` URL scheme can prevent an app from
passing Apple's App Store review process for the macOS and iOS App Stores.
Handling for the ``itms-services`` scheme is always removed on iOS; on
macOS, it *may* be removed if CPython has been built with the
:option:`--with-app-store-compliance` option.

The :mod:`urllib.parse` module defines functions that fall into two broad
categories: URL parsing and URL quoting. These are covered in detail in
the following sections.
Expand Down
11 changes: 11 additions & 0 deletions Doc/using/configure.rst
Original file line number Diff line number Diff line change
Expand Up @@ -945,6 +945,17 @@ See :source:`Mac/README.rst`.
Specify the name for the python framework on macOS only valid when
:option:`--enable-framework` is set (default: ``Python``).

.. option:: --with-app-store-compliance
.. option:: --with-app-store-compliance=PATCH-FILE

The Python standard library contains strings that are known to trigger
automated inspection tool errors when submitted for distribution by
the macOS and iOS App Stores. If enabled, this option will apply the list of
patches that are known to correct app store compliance. A custom patch
file can also be specified. This option is disabled by default.

.. versionadded:: 3.13

iOS Options
-----------

Expand Down
18 changes: 18 additions & 0 deletions Doc/using/ios.rst
Original file line number Diff line number Diff line change
Expand Up @@ -323,3 +323,21 @@ modules in your app, some additional steps will be required:

* If you're using a separate folder for third-party packages, ensure that folder
is included as part of the ``PYTHONPATH`` configuration in step 10.

App Store Compliance
====================

The only mechanism for distributing apps to third-party iOS devices is to
submit the app to the iOS App Store; apps submitted for distribution must pass
Apple's app review process. This process includes a set of automated validation
rules that inspect the submitted application bundle for problematic code.

The Python standard library contains some code that is known to violate these
automated rules. While these violations appear to be false positives, Apple's
review rules cannot be challenged; so, it is necessary to modify the Python
standard library for an app to pass App Store review.

The Python source tree contains
:source:`a patch file <Mac/Resources/app-store-compliance.patch>` that will remove
all code that is known to cause issues with the App Store review process. This
patch is applied automatically when building for iOS.
22 changes: 22 additions & 0 deletions Doc/using/mac.rst
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,28 @@ distributable application:
* `PyInstaller <https://pyinstaller.org/>`__: A cross-platform packaging tool that creates
a single file or folder as a distributable artifact.

App Store Compliance
--------------------

Apps submitted for distribution through the macOS App Store must pass Apple's
app review process. This process includes a set of automated validation rules
that inspect the submitted application bundle for problematic code.

The Python standard library contains some code that is known to violate these
automated rules. While these violations appear to be false positives, Apple's
review rules cannot be challenged. Therefore, it is necessary to modify the
Python standard library for an app to pass App Store review.

The Python source tree contains
:source:`a patch file <Mac/Resources/app-store-compliance.patch>` that will remove
all code that is known to cause issues with the App Store review process. This
patch is applied automatically when CPython is configured with the
:option:`--with-app-store-compliance` option.

This patch is not normally required to use CPython on a Mac; nor is it required
if you are distributing an app *outside* the macOS App Store. It is *only*
required if you are using the macOS App Store as a distribution channel.

Other Resources
===============

Expand Down
29 changes: 29 additions & 0 deletions Mac/Resources/app-store-compliance.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
diff --git a/Lib/test/test_urlparse.py b/Lib/test/test_urlparse.py
index d6c83a75c1c..19ed4e01091 100644
--- a/Lib/test/test_urlparse.py
+++ b/Lib/test/test_urlparse.py
@@ -237,11 +237,6 @@ def test_roundtrips(self):
'','',''),
('git+ssh', 'git@github.com','/user/project.git',
'', '')),
- ('itms-services://?action=download-manifest&url=https://example.com/app',
- ('itms-services', '', '', '',
- 'action=download-manifest&url=https://example.com/app', ''),
- ('itms-services', '', '',
- 'action=download-manifest&url=https://example.com/app', '')),
('+scheme:path/to/file',
('', '', '+scheme:path/to/file', '', '', ''),
('', '', '+scheme:path/to/file', '', '')),
diff --git a/Lib/urllib/parse.py b/Lib/urllib/parse.py
index 8f724f907d4..148caf742c9 100644
--- a/Lib/urllib/parse.py
+++ b/Lib/urllib/parse.py
@@ -59,7 +59,7 @@
'imap', 'wais', 'file', 'mms', 'https', 'shttp',
'snews', 'prospero', 'rtsp', 'rtsps', 'rtspu', 'rsync',
'svn', 'svn+ssh', 'sftp', 'nfs', 'git', 'git+ssh',
- 'ws', 'wss', 'itms-services']
+ 'ws', 'wss']

uses_params = ['', 'ftp', 'hdl', 'prospero', 'http', 'imap',
'https', 'shttp', 'rtsp', 'rtsps', 'rtspu', 'sip',
23 changes: 22 additions & 1 deletion Makefile.pre.in
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,9 @@ EXPORTSFROM= @EXPORTSFROM@
EXE= @EXEEXT@
BUILDEXE= @BUILDEXEEXT@

# Name of the patch file to apply for app store compliance
APP_STORE_COMPLIANCE_PATCH=@APP_STORE_COMPLIANCE_PATCH@

# Short name and location for Mac OS X Python framework
UNIVERSALSDK=@UNIVERSALSDK@
PYTHONFRAMEWORK= @PYTHONFRAMEWORK@
Expand Down Expand Up @@ -692,7 +695,7 @@ list-targets:
@grep -E '^[A-Za-z][-A-Za-z0-9]+:' Makefile | awk -F : '{print $$1}'

.PHONY: build_all
build_all: check-clean-src $(BUILDPYTHON) platform sharedmods \
build_all: check-clean-src check-app-store-compliance $(BUILDPYTHON) platform sharedmods \
gdbhooks Programs/_testembed scripts checksharedmods rundsymutil

.PHONY: build_wasm
Expand All @@ -715,6 +718,16 @@ check-clean-src:
exit 1; \
fi

# Check that the app store compliance patch can be applied (if configured).
# This is checked as a dry-run against the original library sources;
# the patch will be actually applied during the install phase.
.PHONY: check-app-store-compliance
check-app-store-compliance:
@if [ "$(APP_STORE_COMPLIANCE_PATCH)" != "" ]; then \
patch --dry-run --quiet --force --strip 1 --directory "$(abs_srcdir)" --input "$(abs_srcdir)/$(APP_STORE_COMPLIANCE_PATCH)"; \
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Some explanation of the choices here:

  • --dry-run means no changes are applied
  • --quiet means no "patching xyz.py" messages are output
  • --force means no questions are asked of the user, and the patch is assumed to be forward.

echo "App store compliance patch can be applied."; \
fi

# Profile generation build must start from a clean tree.
profile-clean-stamp:
$(MAKE) clean
Expand Down Expand Up @@ -2570,6 +2583,14 @@ libinstall: all $(srcdir)/Modules/xxmodule.c
$(INSTALL_DATA) `cat pybuilddir.txt`/_sysconfigdata_$(ABIFLAGS)_$(MACHDEP)_$(MULTIARCH).py \
$(DESTDIR)$(LIBDEST); \
$(INSTALL_DATA) $(srcdir)/LICENSE $(DESTDIR)$(LIBDEST)/LICENSE.txt
@ # If app store compliance has been configured, apply the patch to the
@ # installed library code. The patch has been previously validated against
@ # the original source tree, so we can ignore any errors that are raised
@ # due to files that are missing because of --disable-test-modules etc.
@if [ "$(APP_STORE_COMPLIANCE_PATCH)" != "" ]; then \
echo "Applying app store compliance patch"; \
patch --force --reject-file "$(abs_builddir)/app-store-compliance.rej" --strip 2 --directory "$(DESTDIR)$(LIBDEST)" --input "$(abs_srcdir)/$(APP_STORE_COMPLIANCE_PATCH)" || true ; \
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Some explanation of the choices here:

  • --force means no questions are asked of the user, and the patch is assumed to be forward.
  • --reject-file provides a file where rejected chunks will be output.

The --reject-file option is required because installing when --disable-test-modules is enabled results in a failure patching test/test_urlparse.py - which then fails again because the test folder doesn't exist, so it can't write a .rej file. If the directory did exist, there would be a .rej file in the distributed artefact.

.orig files shouldn't be generated because we know the patch applies cleanly as a result of pre-validation.

fi
@ # Build PYC files for the 3 optimization levels (0, 1, 2)
-PYTHONPATH=$(DESTDIR)$(LIBDEST) $(RUNSHARED) \
$(PYTHON_FOR_BUILD) -Wi $(DESTDIR)$(LIBDEST)/compileall.py \
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Added a :option:`--with-app-store-compliance` option to patch out known
issues with macOS/iOS App Store review processes.
53 changes: 53 additions & 0 deletions configure

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

41 changes: 41 additions & 0 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -695,6 +695,47 @@ AC_SUBST([INSTALLTARGETS])
AC_DEFINE_UNQUOTED([_PYTHONFRAMEWORK], ["${PYTHONFRAMEWORK}"],
[framework name])

dnl quadrigraphs "@<:@" and "@:>@" produce "[" and "]" in the output
AC_MSG_CHECKING([for --with-app-store-compliance])
AC_ARG_WITH(
[app_store_compliance],
[AS_HELP_STRING(
[--with-app-store-compliance=@<:@PATCH-FILE@:>@],
[Enable any patches required for compiliance with app stores.
Optional PATCH-FILE specifies the custom patch to apply.]
)],[
case "$withval" in
yes)
case $ac_sys_system in
Darwin|iOS)
# iOS is able to share the macOS patch
APP_STORE_COMPLIANCE_PATCH="Mac/Resources/app-store-compliance.patch"
;;
*) AC_MSG_ERROR([no default app store compliance patch available for $ac_sys_system]) ;;
esac
AC_MSG_RESULT([applying default app store compliance patch])
;;
*)
APP_STORE_COMPLIANCE_PATCH="${withval}"
AC_MSG_RESULT([applying custom app store compliance patch])
;;
esac
],[
case $ac_sys_system in
iOS)
# Always apply the compliance patch on iOS; we can use the macOS patch
APP_STORE_COMPLIANCE_PATCH="Mac/Resources/app-store-compliance.patch"
AC_MSG_RESULT([applying default app store compliance patch])
;;
*)
# No default app compliance patching on any other platform
APP_STORE_COMPLIANCE_PATCH=
AC_MSG_RESULT([not patching for app store compliance])
;;
esac
])
AC_SUBST([APP_STORE_COMPLIANCE_PATCH])

AC_SUBST([_PYTHON_HOST_PLATFORM])
if test "$cross_compiling" = yes; then
case "$host" in
Expand Down
Loading