Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

gh-114099 - Add iOS framework loading machinery.#116454

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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
ned-deily merged 11 commits intopython:mainfromfreakboy3742:ios-framework-loader
Mar 19, 2024
Merged
Show file tree
Hide file tree
Changes fromall commits
Commits
Show all changes
11 commits
Select commitHold shift + click to select a range
e907723
Add iOS framework loading machinery.
freakboy3742Mar 7, 2024
1359bc8
Removed a stray character.
freakboy3742Mar 7, 2024
5e0659e
Simplify logic for dylib extensions.
freakboy3742Mar 7, 2024
d5fda7e
Clarified docstrings and implementation on iOS Loader/Finder, and add…
freakboy3742Mar 8, 2024
284e225
Add iOS to the list of platforms known by documentation.
freakboy3742Mar 8, 2024
9f42faa
Improvements to documentation.
freakboy3742Mar 11, 2024
8308611
Modify loader to do more processing in the finder.
freakboy3742Mar 11, 2024
fa3ffbb
Switch to a FileLoader-based approach for Apple framework loading.
freakboy3742Mar 14, 2024
dbf818d
Merge branch 'main' into ios-framework-loader
freakboy3742Mar 14, 2024
e66aee1
Add the ability to reverse a framework back to it's origin location.
freakboy3742Mar 15, 2024
04d1c79
Ensure CFBundleShortVersionString meets App Store guidelines.
freakboy3742Mar 19, 2024
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
2 changes: 1 addition & 1 deletion.gitignore
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -69,7 +69,7 @@ Lib/test/data/*
/_bootstrap_python
/Makefile
/Makefile.pre
iOSTestbed.*
/iOSTestbed.*
iOS/Frameworks/
iOS/Resources/Info.plist
iOS/testbed/build
Expand Down
63 changes: 63 additions & 0 deletionsDoc/library/importlib.rst
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -1241,6 +1241,69 @@ find and load modules.
and how the module's :attr:`__file__` is populated.


.. class:: AppleFrameworkLoader(name, path)

A specialization of :class:`importlib.machinery.ExtensionFileLoader` that
is able to load extension modules in Framework format.

For compatibility with the iOS App Store, *all* binary modules in an iOS app
must be dynamic libraries, contained in a framework with appropriate
metadata, stored in the ``Frameworks`` folder of the packaged app. There can
be only a single binary per framework, and there can be no executable binary
material outside the Frameworks folder.

To accomodate this requirement, when running on iOS, extension module
binaries are *not* packaged as ``.so`` files on ``sys.path``, but as
individual standalone frameworks. To discover those frameworks, this loader
is be registered against the ``.fwork`` file extension, with a ``.fwork``
file acting as a placeholder in the original location of the binary on
``sys.path``. The ``.fwork`` file contains the path of the actual binary in
the ``Frameworks`` folder, relative to the app bundle. To allow for
resolving a framework-packaged binary back to the original location, the
framework is expected to contain a ``.origin`` file that contains the
location of the ``.fwork`` file, relative to the app bundle.

For example, consider the case of an import ``from foo.bar import _whiz``,
where ``_whiz`` is implemented with the binary module
``sources/foo/bar/_whiz.abi3.so``, with ``sources`` being the location
registered on ``sys.path``, relative to the application bundle. This module
*must* be distributed as
``Frameworks/foo.bar._whiz.framework/foo.bar._whiz`` (creating the framework
name from the full import path of the module), with an ``Info.plist`` file
in the ``.framework`` directory identifying the binary as a framework. The
``foo.bar._whiz`` module would be represented in the original location with
a ``sources/foo/bar/_whiz.abi3.fwork`` marker file, containing the path
``Frameworks/foo.bar._whiz/foo.bar._whiz``. The framework would also contain
``Frameworks/foo.bar._whiz.framework/foo.bar._whiz.origin``, containing the
path to the ``.fwork`` file.

When a module is loaded with this loader, the ``__file__`` for the module
will report as the location of the ``.fwork`` file. This allows code to use
the ``__file__`` of a module as an anchor for file system traveral.
However, the spec origin will reference the location of the *actual* binary
in the ``.framework`` folder.

The Xcode project building the app is responsible for converting any ``.so``
files from wherever they exist in the ``PYTHONPATH`` into frameworks in the
``Frameworks`` folder (including stripping extensions from the module file,
the addition of framework metadata, and signing the resulting framework),
and creating the ``.fwork`` and ``.origin`` files. This will usually be done
with a build step in the Xcode project; see the iOS documentation for
details on how to construct this build step.

.. versionadded:: 3.13

.. availability:: iOS.

.. attribute:: name

Name of the module the loader supports.

.. attribute:: path

Path to the ``.fwork`` file for the extension module.


:mod:`importlib.util` -- Utility code for importers
---------------------------------------------------

Expand Down
2 changes: 1 addition & 1 deletionDoc/tools/extensions/pyspecific.py
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -133,7 +133,7 @@ class Availability(SphinxDirective):
known_platforms = frozenset({
"AIX", "Android", "BSD", "DragonFlyBSD", "Emscripten", "FreeBSD",
"GNU/kFreeBSD", "Linux", "NetBSD", "OpenBSD", "POSIX", "Solaris",
"Unix", "VxWorks", "WASI", "Windows", "macOS",
"Unix", "VxWorks", "WASI", "Windows", "macOS", "iOS",
# libc
"BSD libc", "glibc", "musl",
# POSIX platforms with pthreads
Expand Down
11 changes: 11 additions & 0 deletionsLib/ctypes/__init__.py
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -348,6 +348,17 @@ def __init__(self, name, mode=DEFAULT_MODE, handle=None,
winmode=None):
if name:
name = _os.fspath(name)

# If the filename that has been provided is an iOS/tvOS/watchOS
# .fwork file, dereference the location to the true origin of the
# binary.
if name.endswith(".fwork"):
with open(name) as f:
name = _os.path.join(
_os.path.dirname(_sys.executable),
f.read().strip()
)

self._name = name
flags = self._func_flags_
if use_errno:
Expand Down
2 changes: 1 addition & 1 deletionLib/ctypes/util.py
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -67,7 +67,7 @@ def find_library(name):
return fname
return None

elif os.name == "posix" and sys.platform=="darwin":
elif os.name == "posix" and sys.platformin {"darwin", "ios", "tvos", "watchos"}:
from ctypes.macholib.dyld import dyld_find as _dyld_find
def find_library(name):
possible = ['lib%s.dylib' % name,
Expand Down
53 changes: 50 additions & 3 deletionsLib/importlib/_bootstrap_external.py
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -52,7 +52,7 @@

# Bootstrap-related code ######################################################
_CASE_INSENSITIVE_PLATFORMS_STR_KEY = 'win',
_CASE_INSENSITIVE_PLATFORMS_BYTES_KEY = 'cygwin', 'darwin'
_CASE_INSENSITIVE_PLATFORMS_BYTES_KEY = 'cygwin', 'darwin', 'ios', 'tvos', 'watchos'
_CASE_INSENSITIVE_PLATFORMS = (_CASE_INSENSITIVE_PLATFORMS_BYTES_KEY
+ _CASE_INSENSITIVE_PLATFORMS_STR_KEY)

Expand DownExpand Up@@ -1711,6 +1711,46 @@ def __repr__(self):
return f'FileFinder({self.path!r})'


class AppleFrameworkLoader(ExtensionFileLoader):
"""A loader for modules that have been packaged as frameworks for
compatibility with Apple's iOS App Store policies.
"""
def create_module(self, spec):
# If the ModuleSpec has been created by the FileFinder, it will have
# been created with an origin pointing to the .fwork file. We need to
# redirect this to the location in the Frameworks folder, using the
# content of the .fwork file.
if spec.origin.endswith(".fwork"):
with _io.FileIO(spec.origin, 'r') as file:
framework_binary = file.read().decode().strip()
bundle_path = _path_split(sys.executable)[0]
spec.origin = _path_join(bundle_path, framework_binary)

# If the loader is created based on the spec for a loaded module, the
# path will be pointing at the Framework location. If this occurs,
# get the original .fwork location to use as the module's __file__.
if self.path.endswith(".fwork"):
path = self.path
else:
with _io.FileIO(self.path + ".origin", 'r') as file:
origin = file.read().decode().strip()
bundle_path = _path_split(sys.executable)[0]
path = _path_join(bundle_path, origin)

module = _bootstrap._call_with_frames_removed(_imp.create_dynamic, spec)

_bootstrap._verbose_message(
"Apple framework extension module {!r} loaded from {!r} (path {!r})",
spec.name,
spec.origin,
path,
)

# Ensure that the __file__ points at the .fwork location
module.__file__ = path

return module

# Import setup ###############################################################

def _fix_up_module(ns, name, pathname, cpathname=None):
Expand DownExpand Up@@ -1743,10 +1783,17 @@ def _get_supported_file_loaders():

Each item is a tuple (loader, suffixes).
"""
extensions = ExtensionFileLoader, _imp.extension_suffixes()
if sys.platform in {"ios", "tvos", "watchos"}:
extension_loaders = [(AppleFrameworkLoader, [
suffix.replace(".so", ".fwork")
for suffix in _imp.extension_suffixes()
])]
else:
extension_loaders = []
extension_loaders.append((ExtensionFileLoader, _imp.extension_suffixes()))
source = SourceFileLoader, SOURCE_SUFFIXES
bytecode = SourcelessFileLoader, BYTECODE_SUFFIXES
return[extensions,source, bytecode]
returnextension_loaders + [source, bytecode]


def _set_bootstrap_module(_bootstrap_module):
Expand Down
6 changes: 5 additions & 1 deletionLib/importlib/abc.py
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -180,7 +180,11 @@ def get_code(self, fullname):
else:
return self.source_to_code(source, path)

_register(ExecutionLoader, machinery.ExtensionFileLoader)
_register(
ExecutionLoader,
machinery.ExtensionFileLoader,
machinery.AppleFrameworkLoader,
)


class FileLoader(_bootstrap_external.FileLoader, ResourceLoader, ExecutionLoader):
Expand Down
1 change: 1 addition & 0 deletionsLib/importlib/machinery.py
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -12,6 +12,7 @@
from ._bootstrap_external import SourceFileLoader
from ._bootstrap_external import SourcelessFileLoader
from ._bootstrap_external import ExtensionFileLoader
from ._bootstrap_external import AppleFrameworkLoader
from ._bootstrap_external import NamespaceLoader


Expand Down
7 changes: 6 additions & 1 deletionLib/inspect.py
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -954,6 +954,10 @@ def getsourcefile(object):
elif any(filename.endswith(s) for s in
importlib.machinery.EXTENSION_SUFFIXES):
return None
elif filename.endswith(".fwork"):
# Apple mobile framework markers are another type of non-source file
return None

# return a filename found in the linecache even if it doesn't exist on disk
if filename in linecache.cache:
return filename
Expand DownExpand Up@@ -984,6 +988,7 @@ def getmodule(object, _filename=None):
return object
if hasattr(object, '__module__'):
return sys.modules.get(object.__module__)

# Try the filename to modulename cache
if _filename is not None and _filename in modulesbyfile:
return sys.modules.get(modulesbyfile[_filename])
Expand DownExpand Up@@ -1119,7 +1124,7 @@ def findsource(object):
# Allow filenames in form of "<something>" to pass through.
# `doctest` monkeypatches `linecache` module to enable
# inspection, so let `linecache.getlines` to be called.
if not (file.startswith('<') and file.endswith('>')):
if(not (file.startswith('<') and file.endswith('>'))) or file.endswith('.fwork'):
raise OSError('source code not available')

module = getmodule(object, file)
Expand Down
7 changes: 6 additions & 1 deletionLib/modulefinder.py
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -72,7 +72,12 @@ def _find_module(name, path=None):
if isinstance(spec.loader, importlib.machinery.SourceFileLoader):
kind = _PY_SOURCE

elif isinstance(spec.loader, importlib.machinery.ExtensionFileLoader):
elif isinstance(
spec.loader, (
importlib.machinery.ExtensionFileLoader,
importlib.machinery.AppleFrameworkLoader,
)
):
kind = _C_EXTENSION

elif isinstance(spec.loader, importlib.machinery.SourcelessFileLoader):
Expand Down
16 changes: 14 additions & 2 deletionsLib/test/test_capi/test_misc.py
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -2001,14 +2001,21 @@ def test_module_state_shared_in_global(self):
self.addCleanup(os.close, r)
self.addCleanup(os.close, w)

# Apple extensions must be distributed as frameworks. This requires
# a specialist loader.
if support.is_apple_mobile:
loader = "AppleFrameworkLoader"
else:
loader = "ExtensionFileLoader"

script = textwrap.dedent(f"""
import importlib.machinery
import importlib.util
import os

fullname = '_test_module_state_shared'
origin = importlib.util.find_spec('_testmultiphase').origin
loader = importlib.machinery.ExtensionFileLoader(fullname, origin)
loader = importlib.machinery.{loader}(fullname, origin)
spec = importlib.util.spec_from_loader(fullname, loader)
module = importlib.util.module_from_spec(spec)
attr_id = str(id(module.Error)).encode()
Expand DownExpand Up@@ -2316,7 +2323,12 @@ class Test_ModuleStateAccess(unittest.TestCase):
def setUp(self):
fullname = '_testmultiphase_meth_state_access' # XXX
origin = importlib.util.find_spec('_testmultiphase').origin
loader = importlib.machinery.ExtensionFileLoader(fullname, origin)
# Apple extensions must be distributed as frameworks. This requires
# a specialist loader.
if support.is_apple_mobile:
loader = importlib.machinery.AppleFrameworkLoader(fullname, origin)
else:
loader = importlib.machinery.ExtensionFileLoader(fullname, origin)
spec = importlib.util.spec_from_loader(fullname, loader)
module = importlib.util.module_from_spec(spec)
loader.exec_module(module)
Expand Down
Loading

[8]ページ先頭

©2009-2025 Movatter.jp