Following system colour scheme Selected dark colour scheme Selected light colour scheme

Python 개선 제안 한국어 번역

PEP 730 – 지원 플랫폼으로 iOS 추가

Author:
Russell Keith-Magee <russell at keith-magee.com>
Sponsor:
Ned Deily <nad at python.org>
Discussions-To:
Discourse thread
Status:
Final
Type:
Standards Track
Created:
09-Oct-2023
Python-Version:
3.13
Resolution:
Discourse message

Table of Contents

번역·라이선스 안내

이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain or CC0-1.0, whichever is more permissive 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판

Important

This PEP is a historical document. The up-to-date, canonical documentation can now be found at Using Python on iOS.

×

See PEP 1 for how to propose changes.

초록

이 PEP는 CPython에 iOS를 지원 플랫폼으로 추가할 것을 제안합니다. 초기 목표는 Python 3.13에서 Tier 3 지원을 달성하는 것입니다. 이 PEP는 iOS를 지원하는 데 필요한 변경 사항의 기술적 측면을 설명합니다. 또한 iOS를 Tier 3 플랫폼으로 채택하는 것과 관련된 프로젝트 관리상의 고려 사항도 설명합니다.

동기

지난 15년 동안 모바일 플랫폼은 컴퓨팅 환경에서 점점 더 중요한 부분이 되었습니다. iOS는 이러한 기기의 대다수를 차지하는 두 운영 체제 중 하나입니다. 그러나 CPython에는 iOS에 대한 공식 지원이 없습니다.

BeeWare Project와 Kivy는 모두 거의 10년 동안 iOS를 지원해 왔습니다. 이 지원을 통해 만든 애플리케이션이 iOS App Store 게시 승인을 받은 바 있습니다. 이는 iOS 지원이 기술적으로 실현 가능함을 보여 줍니다.

언어로서 Python의 미래를 위해서는 널리 채택된 모든 하드웨어나 OS에서 Python을 사용할 수 있어야 합니다. 널리 사용되는 플랫폼에서 Python을 사용할 수 없다면, 잠재적 사용자들이 이러한 플랫폼을 실제로 지원하는 다른 언어를 채택하게 되므로 언어의 채택에 영향을 받게 됩니다.

근거

개발 환경

iOS는 단일 API를 제공하지만, ABI는 iphoneos (물리 기기)와 iphonesimulator의 두 가지로 구분됩니다. 이러한 각 ABI는 여러 CPU 아키텍처에서 제공될 수 있습니다. 이 글을 쓰는 시점에 Apple은 기기 ABI에서 arm64를 공식 지원하며, 시뮬레이터 ABI에서는 arm64와 x86_64를 지원합니다.

macOS와 마찬가지로 iOS는 여러 CPU 아키텍처를 포함하는 “팻(fat)” 바이너리 생성을 지원합니다. 그러나 팻 바이너리는 여러 ABI에 걸쳐 있을 수 없습니다. 즉, 팻 시뮬레이터 바이너리와 팻 기기 바이너리를 각각 만들 수는 있지만, 시뮬레이터와 기기의 요구를 모두 충족하는 단일 팻 “iOS” 바이너리를 만들 수는 없습니다. 단일 개발 산출물의 배포를 지원하기 위해 Apple은 공통 API를 구현하는 여러 ABI를 감싸는 래퍼인 “XCframework” 구조를 사용합니다.

iOS는 macOS와 마찬가지로 Darwin 커널에서 실행됩니다. 그러나 iOS와 macOS 사이에는 상당한 플랫폼 차이가 있으므로, 구현 수준에서 macOS와 iOS를 구별할 필요가 있습니다.

iOS 코드는 최소 iOS 버전과의 호환성을 기준으로 컴파일됩니다.

Apple은 마케팅 자료에서 “iPadOS”라는 명칭을 자주 사용합니다. 그러나 개발 관점에서는 iPadOS와 iOS 사이에 식별할 수 있는 차이가 없습니다. iphoneos 또는 iphonesimulator ABI용으로 컴파일된 바이너리는 iPad에 배포할 수 있습니다.

tvOS, watchOS, visionOS 같은 다른 Apple 플랫폼은 다른 ABI를 사용하며, 이 PEP에서 다루지 않습니다.

POSIX 준수

iOS는 대체로 POSIX 플랫폼입니다. 그러나 WASI/Emscripten과 마찬가지로 iOS에는 존재하지만 사용할 수 없는 POSIX API가 있고, 아예 존재하지 않는 POSIX API도 있습니다.

이 가운데 가장 두드러진 점은 iOS가 어떤 형태의 다중 프로세스도 지원하지 않는다는 사실입니다. fork와 spawn은 모두 iOS API에 존재하기는 하지만, 이를 호출하면 호출한 iOS 프로세스가 멈추고 새 프로세스는 시작되지 않습니다.

WASI/Emscripten과 달리 iOS에서는 스레딩이 지원됩니다.

소켓 처리에도 상당한 제약이 있습니다. 프로세스 샌드박싱 때문에 소켓을 통한 프로세스 간 통신은 사용할 수 없습니다. 그러나 네트워크 통신을 위한 소켓은 사용할 수 있습니다.

동적 라이브러리

iOS App Store 가이드라인은 Objective C나 Swift 이외의 언어로 앱을 작성하는 것을 허용합니다. 그러나 배포를 위해 제출되는 앱의 구조에 대해서는 매우 엄격한 지침을 두고 있습니다.

iOS 앱은 동적으로 로드되는 라이브러리를 사용할 수 있지만, 동적으로 로드되는 콘텐츠를 iOS에서 사용할 수 있도록 패키징하는 방식에는 매우 엄격한 요구 사항이 있습니다:

  • 동적 바이너리 콘텐츠는 공유 객체나 바이너리 번들이 아니라 동적 라이브러리로 컴파일되어야 합니다.
  • 이것들은 앱 번들 안에 프레임워크로 패키징되어야 합니다.
  • 각 프레임워크는 하나의 동적 라이브러리만 포함할 수 있습니다.
  • 프레임워크는 반드시 iOS 앱의 Frameworks 폴더 안에 있어야 합니다.
  • 프레임워크는 라이브러리가 아닌 콘텐츠를 포함할 수 없습니다.

이로 인해 CPython의 동작에 몇 가지 제약이 생깁니다. 바이너리 모듈을 lib-dynload나 site-packages 폴더 또는 두 폴더 모두에 저장할 수 없습니다. 바이너리 모듈은 앱의 Frameworks 폴더에 저장되어야 하며, 각 모듈은 하나의 프레임워크로 감싸져야 합니다. 이는 또한 Python 모듈이 자신의 __file__ 속성을 사용하여 바이너리 모듈의 위치를 구성할 수 있다는 일반적인 가정이 더 이상 성립하지 않는다는 뜻입니다.

macOS와 마찬가지로, 정적으로 링크된 Python 빌드에서 접근할 수 있는 바이너리 모듈을 컴파일하려면 모든 바이너리 모듈에 libpython3.x를 링크하지 않도록 --undefined dynamic_lookup 옵션을 사용해야 합니다. 그러나 iOS에서는 이 컴파일러 플래그를 사용하면 폐지 예정 경고가 발생합니다. 이 플래그로 인한 경고는 macOS에서도 관찰되었지만, Apple 직원들의 답변을 보면 이 옵션을 제거하여 CPython 생태계를 망가뜨릴 의도는 없는 것으로 보입니다. 현재 Python은 iOS에서 눈에 띄는 입지를 갖고 있지 않으므로, iOS에서 이 플래그를 사용하는 경우에도 같은 방침이 적용될지 판단하기 어렵습니다.

콘솔 및 대화형 사용

전통적인 CPython REPL이나 대화형 “python.exe”를 배포하는 것을 이 작업의 목표로 간주해서는 안 됩니다.

모바일 기기(iOS 포함)는 TTY 스타일의 콘솔을 제공하지 않습니다. 모바일 기기는 stdin, stdout, stderr를 제공하지 않습니다. iOS는 시스템 로그를 제공하며, 모든 stdout과 stderr 내용이 시스템 로그로 전달되도록 리디렉션을 설치할 수 있지만, stdin에 해당하는 것은 없습니다.

또한 iOS는 런타임에 추가 코드를 다운로드하는 것을 제한합니다(이러한 동작은 기능적으로 App Store 심사를 우회하려는 시도와 구별할 수 없기 때문입니다). 따라서 “가상 환경을 만들고 pip install을 하는” 전통적인 개발 방식은 iOS에서 사용할 수 없습니다.

REPL 인터페이스를 제공하는 네이티브 iOS 애플리케이션을 만드는 것은 가능한 일입니다. 이는 IDLE 방식의 사용자 경험에 더 가까울 것입니다. 하지만 iOS에서는 Tkinter를 사용할 수 없으므로 어떤 앱이든 처음부터 다시 작성해야 합니다. iOS 앱 스토어에는 이미 이 범주에 속하는 앱이 여러 개 있습니다(예: Pythonista와 Pyto). 이 작업의 초점은 IDE 방식의 네이티브 인터페이스가 활용할 수 있는 임베디드 배포판을 제공하는 것이며, Python에서 iOS를 다루는 사용자용 “앱” 인터페이스를 제공하는 것이 아닙니다.

명세

플랫폼 식별

sys

sys.platform은 시뮬레이터와 실제 기기 모두에서 "ios"로 식별됩니다.

sys.implementation._multiarch는 ABI와 CPU 아키텍처를 나타냅니다:

  • ARM64 기기의 경우 "arm64-iphoneos"
  • ARM64 시뮬레이터의 경우 "arm64-iphonesimulator"
  • x86_64 시뮬레이터의 경우 "x86_64-iphonesimulator"

platform

platform은 iOS 고유의 세부 정보를 반환할 수 있도록 수정됩니다. platform 모듈이 반환하는 값은 대부분 os.uname()이 반환하는 값과 같지만, 다음은 예외입니다:

  • platform.system() - "Darwin" 대신 "iOS" 또는 iPadOS (사용 중인 하드웨어에 따라 다름)
  • platform.release() - Darwin 커널 버전 대신 문자열 형태의 iOS 버전 번호 (예: "16.6.1").

또한 platform.ios_ver() 메서드가 추가됩니다. 이는 macOS 버전 정보를 제공하는 데 사용할 수 있는 platform.mac_ver()를 본뜬 것입니다. ios_ver()는 다음 항목을 포함하는 namedtuple을 반환합니다:

  • system - OS 이름 (하드웨어에 따라 iOS 또는 iPadOS)
  • release - 문자열 형태의 iOS 버전 (예: "16.6.1").
  • model - 문자열 형태의 기기 모델 식별자 (예: "iPhone13,2"). 시뮬레이터에서는 시뮬레이터 기기에 따라 "iPhone" 또는 "iPad"를 반환합니다.
  • is_simulator - 기기가 시뮬레이터인지를 나타내는 불리언 값.

os

os.uname()은 POSIX uname() 호출의 원시 결과를 반환합니다. 그 결과 다음 값을 얻게 됩니다:

  • sysname - "Darwin"
  • release - Darwin 커널 버전 (예: "22.6.0")

이 접근 방식은 os 모듈을 시스템 API에 대한 “원시” 인터페이스로, platform을 더 일반적으로 유용한 값을 제공하는 상위 수준 API로 취급합니다.

sysconfig

sysconfig 모듈은 최소 iOS 버전을 sysconfig.get_platform()의 일부로 사용합니다(예: "ios-12.0-arm64-iphoneos"). sysconfigdata_name과 Config 메이크파일은 식별자를 구성할 때 기존 플랫폼과 같은 패턴(sys.platform, sys.implementation._multiarch 등을 사용)을 따릅니다.

서브프로세스 지원

iOS는 WASI/Emscripten에서 확립된 서브프로세스 비활성화 패턴을 활용합니다. 서브프로세스를 시작하려고 하면 subprocess 모듈은 예외를 발생시키고, os.fork와 os.spawn 호출은 OSError를 발생시킵니다.

동적 모듈 로딩

iOS의 동적 로딩을 수용하기 위해, importlib 부트스트랩을 확장하여 Python 바이너리 모듈 요청을 프레임워크 위치로 변환할 수 있는 메타패스 파인더를 추가합니다. 이 파인더는 sys.platform == "ios"인 경우에만 설치됩니다.

이 파인더는 전체 모듈 이름을 프레임워크 이름으로 사용하여(즉, foo.bar._whiz.framework) Python 모듈 이름(예: foo.bar._whiz)을 고유한 Framework 이름으로 변환합니다. 프레임워크는 디렉터리이며, 파인더는 그 디렉터리에서 foo.bar._whiz라는 이름의 바이너리를 찾습니다.

컴파일

지원되는 유일한 바이너리 형식은 iOS 호환 프레임워크 형식으로 패키징된, 동적으로 링크 가능한 libpython3.x.dylib입니다. --undefined dynamic_lookup 컴파일러 옵션이 현재는 동작하지만, 이 옵션을 장기적으로 계속 사용할 수 있을지는 보장할 수 없습니다. 미래가 불확실한 컴파일러 플래그에 의존하는 대신, iOS의 바이너리 모듈은 libpython3.x.dylib에 링크됩니다. 이는 libpython3.x.a에 정적으로 링크된 실행 파일에서는 iOS 바이너리 모듈을 로드할 수 없다는 뜻입니다. 따라서 정적 libpython3.x.a iOS 라이브러리는 지원되지 않습니다. 이는 Windows의 CPython에서 사용하는 것과 같은 패턴입니다.

iOS용 CPython을 빌드하려면 CPython의 configure 빌드 시스템에 있는 크로스 플랫폼 도구를 사용해야 합니다. configure/make/make install을 한 번 수행하면 단일 ABI와 아키텍처에서 사용할 수 있는 Python.framework 산출물이 만들어집니다.

여러 아키텍처용 Python.framework 빌드를 하나의 “fat” 라이브러리로 병합하려면 추가 도구가 필요합니다. 또한 여러 ABI를 XCframework 형식으로 병합하는 도구도 필요합니다. 이 형식은 Apple이 서로 다른 ABI용 프레임워크 여러 개를 하나의 번들로 배포할 때 사용합니다.

CPython 테스트 스위트를 실행하기 위한 Xcode 프로젝트가 제공됩니다. 테스트 스위트 바이너리를 컴파일하고, 시뮬레이터를 시작하고, 테스트 스위트를 설치하여 실행하는 과정을 자동화하는 도구가 제공됩니다.

배포

iOS를 Tier 3 플랫폼으로 추가하려면 패치하지 않은 CPython 코드 체크아웃에서 iOS 호환 빌드를 컴파일하는 기능만 지원하면 됩니다. 최종 사용자가 사용할 공식 배포 iOS 산출물을 만들 필요는 없습니다.

앞으로 iOS가 Tier 2 또는 Tier 1 지원으로 격상되면, XCframework 패키지를 생성하는 데 사용한 도구로 iOS 배포 산출물을 만들 수 있습니다. 그러면 이를 Windows 임베디드 배포판과 유사한 “임베디드 배포판”으로 배포하거나, Xcode 프로젝트에 추가할 수 있는 CocoaPod 또는 Swift 패키지로 배포할 수 있습니다.

CI 자원

Anaconda가 iOS 빌드봇을 실행할 물리적 하드웨어를 제공하겠다고 제안했습니다.

GitHub Actions는 macOS 머신에서 iOS 시뮬레이터를 호스팅할 수 있으며, iOS 시뮬레이터는 스크립팅 환경으로 제어할 수 있습니다. 무료 등급은 현재 x86_64 macOS 머신만 제공하지만, ARM64 러너는 최근 유료 요금제에서 사용할 수 있게 되었습니다. 그러나 macOS 러너 자원이 고갈되지 않도록 iOS용 GitHub Actions 실행은 표준 CI 구성에 추가하지 않을 것입니다.

패키징

iOS는 “유니버설” 휠 형식을 제공하지 않을 것입니다. 대신 ABI와 아키텍처의 조합마다 휠을 제공할 것입니다.

iOS 휠은 다음 태그를 사용할 것입니다:

  • ios_12_0_arm64_iphoneos
  • ios_12_0_arm64_iphonesimulator
  • ios_12_0_x86_64_iphonesimulator

이 태그에서 “12.0”은 지원하는 최소 iOS 버전입니다. macOS와 마찬가지로 태그에는 휠을 컴파일할 때 선택한 최소 iOS 버전이 들어갑니다. 최소 iOS 버전을 15.0으로 하여 컴파일한 휠은 ios_15_0_* 태그를 사용합니다. 이 글을 쓰는 시점에 iOS 12.0은 중요한 iOS 기능 대부분을 제공하면서도 100%에 가까운 기기를 포괄하므로, 이 버전을 iOS 버전 매칭의 하한으로 사용할 것입니다.

이러한 휠은 바이너리 모듈을 제자리에(즉, 데스크톱 플랫폼용 휠과 같은 방식으로 Python 소스와 같은 위치에) 포함할 수 있습니다. 그러나 배포를 위해서는 바이너리 모듈을 “Frameworks”(프레임워크) 위치로 옮겨야 하므로 후처리가 필요합니다. 이 작업은 Xcode 빌드 단계로 자동화할 수 있습니다.

PEP 11 갱신

PEP 11을 갱신하여 다음 두 가지 iOS ABI를 포함할 것입니다:

  • arm64-apple-ios
  • arm64-apple-ios-simulator

Ned Deily가 이 ABI들에 대한 초기 코어 팀 담당자를 맡을 것입니다.

x86_64-apple-ios-simulator 타깃은 최선 노력 방식으로 지원하지만, 티어 3 지원 대상으로 삼지는 않을 것입니다. 이는 x86_64가 시뮬레이션 플랫폼으로서 곧 폐지될 예정인 데다, 현시점에서 x86_64 macOS 하드웨어를 마련하기 어렵기 때문입니다.

하위 호환성

새 플랫폼을 추가해도 CPython 자체에는 하위 호환성 문제가 생기지 않습니다.

다만 CPython 패치의 최종 형태가 그동안 CPython 지원을 제공해 온 프로젝트(즉, BeeWare와 Kivy)가 사용해 온 패치와 맞지 않으면, 해당 프로젝트에 하위 호환성 영향이 있을 수 있습니다.

엄밀히 말해 하위 호환성 문제는 아니지만, 플랫폼 채택과 관련하여 고려할 점이 있습니다. CPython 자체가 iOS를 지원하더라도 iOS 호환 휠을 만드는 방법이 명확하지 않고 cryptography, Pillow, NumPy 같은 주요 라이브러리가 iOS 휠을 제공하지 않는다면, 커뮤니티가 iOS에서 Python을 채택하는 데 한계가 있을 것입니다. 따라서 프로젝트가 CI와 릴리스 도구에 iOS 빌드를 추가하는 방법을 명확하게 문서화해야 합니다. crossenv나 cibuildwheel 같은 도구에 iOS 지원을 추가하는 것이 이를 달성하는 한 가지 방법이 될 수 있습니다.

보안 영향

iOS를 새 플랫폼으로 추가해도 보안상 영향은 없습니다.

교육 방법

이 PEP와 관련된 교육 요구 사항은 주로 최종 사용자가 자신의 Xcode 프로젝트에 iOS 지원을 추가하는 방법과 관련됩니다. 이는 해당 과정에 대한 문서와 튜토리얼로 해결할 수 있습니다. 지원 수준이 Tier 3에서 Tier 2 또는 Tier 1로 올라가면 이 문서의 필요성은 커질 것입니다. 다만 이러한 전환에는 Xcode 개발과 통합되는 간소화된 배포 아티팩트(예: Cocoapod 또는 Swift 패키지)도 함께 제공되어야 합니다.

참조 구현

BeeWare Python-Apple-support 저장소에는 배포 가능한 아티팩트를 컴파일하기 위한 참조 패치와 빌드 도구가 들어 있습니다.

Briefcase는 iOS 시뮬레이터에서 테스트 스위트를 실행하는 코드의 참조 구현을 제공합니다. Toga Testbed는 GitHub Actions를 사용하여 iOS 시뮬레이터에서 실행되는 테스트 스위트의 예입니다.

기각된 아이디어

시뮬레이터 식별

이 PEP의 이전 버전에서는 코드가 기기에서 실행 중인지 시뮬레이터에서 실행 중인지 식별하기 위해 sys.implementation._simulator 속성을 포함할 것을 제안했습니다. 이 제안은 공개 API에 보호된 이름을 사용한다는 점과 iOS 전용 세부 사항으로 sys 네임스페이스를 오염시킨다는 점 때문에 거부되었습니다.

논의 중에 나온 또 다른 제안은 모든 플랫폼에서 구현할 수 있는 범용 platform.is_emulator() API를 포함하자는 것이었습니다. 예를 들어 ARM64 하드웨어에서 x86_64 코드를 실행하는 경우나 QEMU 또는 기타 가상화 방식에서 실행하는 경우를 구별하는 데 사용할 수 있습니다. 이 제안은 “에뮬레이터”를 일관되게 해석하는 방법이 무엇인지, 그리고 iOS 외의 경우에 에뮬레이터를 어떻게 감지할지가 명확하지 않다는 이유로 거부되었습니다.

이 세부 사항은 iOS 전용으로 유지하고 platform.ios_ver() API에 포함하기로 결정했습니다.

GNU 컴파일러 트리플

autoconf는 빌드 플랫폼과 호스트 플랫폼을 식별하기 위해 GNU 컴파일러 트리플을 사용해야 합니다. 그러나 autoconf 툴체인은 iOS 시뮬레이터를 기본적으로 지원하지 않으므로, iOS 하드웨어를 GNU의 명명 체계에 어떻게 끼워 맞출지 알아내는 과제가 남습니다.

이는 (config.sub를 일부 패치하면) 가능하지만, 명명 불일치의 주요 원인 두 가지를 낳습니다:

  • 64비트 ARM 하드웨어의 식별자로 arm64와 aarch64 중 무엇을 사용할지, 그리고
  • 시뮬레이터를 나타내는 데 어떤 식별자를 사용할지.

Apple의 자체 도구는 아키텍처로 arm64를 사용하지만, 경우에 따라 aarch64도 허용하는 것으로 보입니다. 기기 플랫폼은 iphoneos와 iphonesimulator로 식별됩니다.

Rust 툴체인은 아키텍처로 aarch64를 사용하고, 기기 플랫폼을 식별하기 위해 aarch64-apple-ios와 aarch64-apple-ios-sim을 사용합니다. 그러나 x86_64 하드웨어의 iOS 시뮬레이터를 나타낼 때는 x86_64-apple-ios를 사용합니다.

다음과 같은 이유로 arm64-apple-ios와 arm64-apple-ios-simulator를 사용하기로 결정했습니다:

  1. autoconf 툴체인은 이미 config.sub에서 ios를 플랫폼으로 지원하고 있으며, 표현 방법이 없는 것은 시뮬레이터뿐입니다.
  2. 호스트 트리플의 세 번째 부분은 sys.platform으로 사용됩니다.
  3. Apple의 자체 도구는 CPU 아키텍처를 가리킬 때 arm64를 사용하며, GNU 도구에서 사용하는 아키텍처 표기는 빌드 과정 밖에서는 보이지 않습니다.
  4. Apple의 자체 도구는 OS와 무관하게 시뮬레이터 여부를 가리킬 때(예: Swift 하위 모듈의 이름) -simulator 접미사를 사용합니다.
  5. 일부 iOS 패키지는 Rust를 사용하겠지만, 모든 iOS 패키지는 Apple의 도구를 사용합니다.

이 문서의 최초 수락 버전에서는 PEP 11 식별자로 aarch64 형식을 사용했으나, 종료 처리 과정에서 수정되었습니다.

“유니버설” 휠 형식

macOS는 현재 2가지 CPU 아키텍처를 지원합니다. 최종 사용자의 개발 경험을 돕기 위해 Python은 x86_64와 ARM64 바이너리를 모두 포함하는 “universal2” 휠 형식을 정의합니다.

개념적으로는 이와 유사한 “universal” iOS 휠 형식을 제공하는 것도 가능합니다. 그러나 이 PEP는 2가지 이유로 이 접근 방식을 사용하지 않습니다.

첫째, macOS에서 얻은 경험, 특히 수치 계산 Python 생태계의 경험에 따르면 유니버설 휠은 수용하기가 극도로 어려울 수 있습니다. macOS 네이티브 라이브러리는 강력한 다중 플랫폼 지원을 유지하고 있고 Python 자체도 업데이트되었지만, 업스트림의 비 Python 라이브러리 대다수는 다중 아키텍처 빌드를 지원하지 않습니다. 그 결과 유니버설 휠을 컴파일하려면 필연적으로 여러 번의 컴파일 과정을 거쳐야 하며, 서로 다른 아키텍처용 헤더 파일을 어떻게 배포할지에 대해서도 복잡한 결정을 내려야 합니다. 이러한 복잡성 때문에 (NumPy와 Pillow를 포함한) 많은 인기 프로젝트가 유니버설 휠을 전혀 제공하지 않고, 대신 ARM64 휠과 x86_64 휠을 별도로 제공합니다.

둘째, 과거의 경험에 비추어 보면 iOS에는 훨씬 더 유동적인 “universal” 정의가 필요합니다. 지난 10년 동안 iOS에 적용될 수 있는 “universal”의 해석은 최소한 5가지가 있었으며, 여기에는 기기와 시뮬레이터에서의 armv6, armv7, armv7s, arm64, x86, x86_64 아키텍처의 다양한 조합이 포함됩니다. 지금 당장 정의한다면 “universal-iOS”에는 아마도 시뮬레이터의 x86_64와 arm64, 그리고 기기의 arm64가 포함될 것입니다. 하지만 곧 있을 x86_64 하드웨어의 폐지는 또 다른 해석을 더하게 될 것이며, 향후 arm64e를 새로운 기기 아키텍처로 추가해야 할 수도 있습니다. iOS 휠을 단일 플랫폼 전용으로 규정하면 Python 코어 팀은 갱신된 “universal” 형식을 둘러싼 끝없는 표준화 논의를 피할 수 있습니다.

또한 휠 배포자는 어떤 플랫폼을 지원하는 것이 현실적인지 프로젝트별로 결정할 수 있습니다. 예를 들어 어떤 프로젝트는 x86_64 지원을 중단하거나, Python 생태계의 다른 부분보다 먼저 새로운 아키텍처를 채택하기로 할 수 있습니다. 플랫폼별 휠을 사용하면 이러한 결정을 개별 패키지 배포자에게 맡길 수 있습니다.

이 결정은 배포가 더 복잡해진다는 대가를 치릅니다. 그러나 iOS에서의 배포는 이미 복잡한 과정이며, 도구의 도움을 받는 것이 가장 좋습니다. 현재는 바이너리 병합이 필요하지 않습니다. 기기용 아키텍처가 하나뿐이고, 시뮬레이터 바이너리는 배포 가능한 산출물로 간주되지 않으므로 시뮬레이터용 앱을 빌드하는 데 하나의 아키텍처만 있으면 되기 때문입니다.

정적 빌드 지원

--undefined dynamic_lookup 옵션을 장기적으로 계속 쓸 수 있을지는 보장할 수 없지만, 이 옵션은 실제로 존재하며 동작합니다. 한 가지 방법은 폐지 경고를 무시하고, Apple이 폐지 결정을 철회하거나 폐지를 끝내 확정하지 않기를 바라는 것입니다.

Apple의 의사 결정 과정이 완전히 불투명하다는 점을 고려하면, 이는 좋게 보아도 위험한 선택지입니다. 더 넓은 iOS 개발 생태계가 프레임워크 사용을 권장한다는 사실, 고려해야 할 정적 라이브러리의 레거시 사용 사례가 없다는 사실, 그리고 정적으로 링크된 iOS libpython3.x.a의 유일한 이점이 앱 시작 시간이 아주 약간 줄어드는 것뿐이라는 사실을 함께 고려하면, libpython3.x의 정적 빌드 지원을 생략하는 것은 합리적인 절충안으로 보입니다.

--undefined dynamic_lookup 옵션의 필요성을 없애 줄 macOS에서의 대체 링크 방식에 대한 논의가 있었다는 점은 주목할 만합니다. 다만 이 접근 방식에 대한 논의는 구현상의 복잡성으로 인해 정체된 것으로 보입니다. 그러한 복잡성이 극복된다면 동일한 접근 방식을 iOS에서도 사용할 수 있을 가능성이 매우 높으며, 이는 정적으로 링크된 libpython3.x.a를 실현 가능하게 만들 것입니다.

바이너리 모듈을 libpython3.x.dylib에 링크하기로 한 결정은 향후 정적 libpython3.x.a 빌드의 도입을 복잡하게 만들 것입니다. 다른 바이너리 모듈 링크 방식으로 전환하려면 “동적으로 링크된” iOS 바이너리 모듈과 “정적 호환” iOS 바이너리 모듈을 명확하게 구별할 방법이 필요하기 때문입니다. 그러나 정적 libpython3.x.a에 실질적인 이점이 없다는 점을 고려하면, 이러한 변경이 필요해질 가능성은 낮아 보입니다.

대화형/REPL 모드

모바일 기기에는 명령줄이 없기 때문에 전통적인 python.exe 명령줄 경험은 모바일 기기에서 사실상 실현할 수 없습니다. iOS 앱에는 stdout, stderr, stdin이 없습니다. stdout과 stderr는 시스템 로그로 리디렉션할 수 있지만, stdin의 입력원을 마련하려면 IDLE 스타일의 IDE 경험에 더 가까운, 매우 특수한 사용자 대면 앱을 만들어야 합니다. 따라서 모바일 배포의 대상으로는 “임베디드 모드”에만 집중하기로 결정했습니다.

x86_64 시뮬레이터 지원

Apple은 더 이상 x86_64 하드웨어를 판매하지 않습니다. 그 결과, x86_64 빌드봇을 새로 마련하기가 어려울 수 있습니다. ARM64 하드웨어에서 x86_64 호환 모드로 macOS 바이너리를 실행할 수는 있지만, 테스트 목적으로는 이상적이지 않습니다. 따라서 x86_64 시뮬레이터(x86_64-apple-ios-simulator)는 Tier 3 대상으로 추가되지 않습니다. iOS 지원은 아무런 수정 없이도 x86_64에서 동작할 가능성이 매우 높습니다. 이 결정은 공식 Tier 3 지위에만 영향을 줍니다.

실제 기기 테스트

시뮬레이터에서의 CI 테스트는 비교적 쉽게 지원할 수 있습니다. 실제 기기 테스트는 훨씬 더 어렵습니다. 빌드봇이나 Github Actions 러너를 제공하도록 구성할 수 있는 디바이스 팜을 구하기가 어렵기 때문입니다.

하지만 실제 기기 테스트가 꼭 필요하지는 않을 수 있습니다. 참고할 만한 사례로, Apple의 Xcode Cloud 솔루션은 실제 기기 테스트를 제공하지 않습니다. Apple은 API가 기기와 시뮬레이터 사이에서 일관되며, ARM64 시뮬레이터 테스트만으로도 CPU별 문제를 충분히 드러낼 수 있다는 사실에 의존합니다.

_multiarch 태그의 순서

이 문서의 처음 승인된 버전은 sys.implementation._multiarch(및 휠 태그와 같은 관련 값)에 <platform>-<arch> 순서(예: iphoneos-arm64)를 사용했습니다. 최종 병합된 버전은 <arch>-<platform> 순서(예: arm64-iphoneos)를 사용합니다. 이는 운영 체제보다 아키텍처를 먼저 지정하는 다른 플랫폼(특히 Linux)의 컴파일러 트리플과 일관성을 유지하기 위함입니다.

platform.ios_ver()가 반환하는 값

이 문서의 처음 승인된 버전에는 system 식별자가 포함되어 있지 않았습니다. 이 식별자는 platform.system()의 구현을 지원하기 위해 구현 단계에서 추가되었습니다.

이 문서의 처음 승인된 버전은 또한 min_release가 ios_ver() 결과에 반환된다고 기술했습니다. 최종 버전은 min_release 값을 생략합니다. 이 값은 런타임에는 중요하지 않으며, 바이너리 호환성에만 영향을 주기 때문입니다. 최소 버전은 sysconfig.get_platform()이 반환하는 값에는 포함됩니다. 이 값이 휠(및 기타 바이너리) 호환성을 정의하는 데 사용되기 때문입니다.