PEP 513 – 이식 가능한 Linux 빌드 배포 패키지를 위한 플랫폼 태그
- Author:
- Robert T. McGibbon <rmcgibbo at gmail.com>, Nathaniel J. Smith <njs at pobox.com>
- BDFL-Delegate:
- Alyssa Coghlan <ncoghlan at gmail.com>
- Discussions-To:
- Distutils-SIG list
- Status:
- Superseded
- Type:
- Informational
- Topic:
- Packaging
- Created:
- 19-Jan-2016
- Post-History:
- 19-Jan-2016, 25-Jan-2016, 29-Jan-2016
- Superseded-By:
- 600
- Resolution:
- Distutils-SIG message
번역·라이선스 안내
이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판
초록
이 PEP는 manylinux1_{x86_64,i686}이라는 새로운 Python 패키지 빌드 배포 패키지용 플랫폼 태그를 만들 것을 제안합니다. 이 태그는 외부 의존성을 Linux 커널 및 핵심 사용자 공간 ABI의 표준화되고 제한된 부분 집합으로 한정합니다. 또한 PyPI가 이 플랫폼 태그가 지정된 휠의 업로드 및 배포를 지원하고, pip이 호환되는 플랫폼에서 이러한 패키지를 다운로드하고 설치하는 것을 지원할 것을 제안합니다.
근거
현재 Windows 및 OS X용 바이너리 Python 확장 기능의 배포는 간단합니다. 개발자와 패키지 제작자는 휠(PEP 427, PEP 491)을 빌드하고, 여기에 win32 또는 macosx_10_6_intel과 같은 플랫폼 태그를 지정한 다음 이 휠을 PyPI에 업로드합니다. 사용자는 pip과 같은 도구를 사용하여 이러한 휠을 다운로드하고 설치할 수 있습니다.
Linux의 경우 상황이 훨씬 더 복잡합니다. 일반적으로 한 Linux 배포판에서 빌드된 컴파일된 Python 확장 모듈은 다른 Linux 배포판에서, 심지어 동일한 Linux 배포판을 실행하지만 다른 시스템 라이브러리가 설치된 서로 다른 컴퓨터에서도 작동하지 않습니다.
관련 PEP 425 플랫폼 태그를 사용하는 빌드 도구는 특정 Linux 배포판이나 설치된 시스템 라이브러리에 관한 정보를 추적하지 않으며, 대신 모든 휠에 지나치게 모호한 linux_i686 또는 linux_x86_64 태그를 할당합니다. 이러한 모호성 때문에 한 컴퓨터에서 컴파일된 linux 태그가 지정된 빌드 배포 패키지가 다른 컴퓨터에서 제대로 작동할 것이라고 기대할 수 없으며, 이러한 이유로 PyPI는 Linux용 휠의 업로드를 허용하지 않았습니다.
어떤 Linux 시스템에서든 작동하는 휠 패키지를 컴파일할 수 있다면 이상적일 것입니다. 그러나 PC부터 Android, 사용자 지정 libc를 사용하는 임베디드 시스템에 이르기까지 Linux 시스템은 매우 다양하므로 일반적으로 이를 보장할 수 없습니다.
대신 실제로 충분한 호환성을 갖추어 이 표준을 준수하는 패키지가 일반적으로 사용되는 데스크톱 및 서버 배포판을 사실상 모두 포함한 많은 Linux 시스템에서 작동하도록 하는 커널 및 핵심 사용자 공간 ABI의 표준 부분 집합을 정의합니다. Linux용으로 이처럼 폭넓은 이식성을 갖춘 사전 컴파일 Python 확장 모듈을 배포해 온 회사들이 있기 때문에 이를 알 수 있습니다. 예를 들어 Enthought는 Canopy [4]를, Continuum Analytics는 Anaconda [5]를 배포해 왔습니다.
따라서 이러한 회사에서 얻은 호환성 관련 교훈을 바탕으로 바이너리 Python 휠에 사용할 기준 manylinux1 플랫폼 태그를 정의하고, 이러한 manylinux1 휠의 구축을 지원하는 예비 도구의 구현을 도입합니다.
Linux 간 바이너리 비호환성의 주요 원인
이 사양을 충족하는 휠 패키지가 많은 Linux 플랫폼에서 작동하도록 보장하는 표준을 올바르게 정의하려면, Linux에서 사전 컴파일 바이너리의 이식성을 방해하는 경우가 많은 근본 원인을 이해해야 합니다. 두 가지 주요 원인은 사용자의 시스템에 존재하지 않는 공유 라이브러리에 대한 의존성과 glibc와 같은 특정 핵심 라이브러리의 특정 버전에 대한 의존성입니다.
외부 공유 라이브러리
대부분의 데스크톱 및 서버 Linux 배포판에는 시스템 패키지 관리자가 포함되어 있습니다. 예를 들어 Debian 기반 시스템의 APT, RPM 기반 시스템의 yum, Arch Linux의 pacman 등이 있으며, 이 관리자는 /usr/lib과 같은 시스템 디렉터리에 설치되는 공유 라이브러리의 설치를 비롯한 여러 업무를 관리합니다. 대부분의 복잡한 Python 확장 기능은 이러한 공유 라이브러리 중 하나 이상에 의존하므로, 사용자가 패키지 관리자를 사용하여 적절한 라이브러리와 적절한 버전을 설치했거나, 의존하는 공유 라이브러리의 위치를 런타임 링커에 알리도록 LD_LIBRARY_PATH와 같은 특정 환경 변수를 설정하여 수동으로 설치한 시스템에서만 제대로 작동합니다.
핵심 공유 라이브러리의 버전 관리
Python 확장 모듈의 개발자가 외부 공유 라이브러리를 사용하지 않으려 하더라도, 해당 모듈은 일반적으로 GNU C 라이브러리인 glibc에 동적으로 런타임 의존성을 갖습니다. glibc를 정적으로 링크하는 것은 가능하지만, dlopen()과 같은 일부 중요한 C 함수는 glibc를 정적으로 링크하는 코드에서 호출할 수 없으므로 일반적으로 좋지 않은 방법입니다. 시스템에서 제공하는 glibc에 대한 런타임 공유 라이브러리 의존성은 실제로 피할 수 없습니다.
GNU C 라이브러리의 유지 관리자는 하위 호환성을 위해 엄격한 심볼 버전 관리 체계를 따릅니다. 이를 통해 이전 버전의 glibc에 대해 컴파일된 바이너리가 더 최신 버전의 glibc를 갖춘 시스템에서 실행될 수 있습니다. 반대의 경우는 일반적으로 성립하지 않습니다. 최신 Linux 배포판에서 컴파일된 바이너리는 이전 시스템에서 사용할 수 없는 glibc의 버전 지정 함수에 의존하는 경향이 있습니다.
이로 인해 일반적으로 최신 Linux 배포판에서 컴파일된 휠은 이식성을 갖기 어렵습니다.
manylinux1 정책
이러한 이유로, 폭넓은 이식성을 확보하려면 Python 휠은
- 극히 제한된 외부 공유 라이브러리 집합에만 의존해야 하며,
- 해당 외부 공유 라이브러리의 “오래된” 심볼 버전에만 의존해야 하며,
- 폭넓게 호환되는 커널 ABI에만 의존해야 합니다.
manylinux1 플랫폼 태그를 사용할 수 있으려면 Python 휠은 따라서 (a) 바이너리 실행 파일과 다음 목록에 포함된 SONAME을 가진 라이브러리에 오직 링크되는 컴파일된 코드를 포함하고, 동시에
libpanelw.so.5
libncursesw.so.5
libgcc_s.so.1
libstdc++.so.6
libm.so.6
libdl.so.2
librt.so.1
libc.so.6
libnsl.so.1
libutil.so.1
libpthread.so.0
libresolv.so.2
libX11.so.6
libXext.so.6
libXrender.so.1
libICE.so.6
libSM.so.6
libGL.so.1
libgobject-2.0.so.0
libgthread-2.0.so.0
libglib-2.0.so.0
그리고 (b) 시스템 패키지 관리자가 제공하는 이러한 라이브러리 버전이 포함된 기본 CentOS 5.11 [6] 시스템에서 작동해야 합니다.
Fedora 30이 대신 libcrypt.so.2와 함께 출시된 후, libcrypt.so.1은 사후에 허용 목록에서 제거되었습니다.
CentOS 5는 x86_64 및 i686 아키텍처에서만 사용할 수 있으므로, 현재 manylinux1 정책이 지원하는 아키텍처도 이들뿐입니다.
Debian 기반 시스템에서는 이러한 라이브러리를 다음 패키지에서 제공합니다.
libncurses5 libgcc1 libstdc++6 libc6 libx11-6 libxext6
libxrender1 libice6 libsm6 libgl1-mesa-glx libglib2.0-0
RPM 기반 시스템에서는 이러한 라이브러리를 다음 패키지에서 제공합니다.
ncurses libgcc libstdc++ glibc libXext libXrender
libICE libSM mesa-libGL glib2
이 목록은 Canopy [4] 및 Anaconda [5] 배포판의 외부 공유 라이브러리 의존성을 확인하여 작성되었습니다. 두 배포판 모두 가장 인기 있는 Python 모듈을 폭넓게 포함하며, 실제로 다양한 Linux 시스템에서 작동하는 것이 확인되었습니다.
위에 나열된 허용된 시스템 라이브러리 중 다수는 하위 호환성을 위해 심볼 버전 관리 체계를 사용합니다. 이러한 라이브러리의 CentOS 5.11 버전이 제공하는 최신 심볼 버전은 다음과 같습니다.
GLIBC_2.5
CXXABI_3.4.8
GLIBCXX_3.4.9
GCC_4.2.0
따라서 (b) 요건의 결과로, 위 공유 라이브러리의 버전이 지정된 심볼에 의존하는 휠은 다음 버전의 심볼에만 의존할 수 있습니다.
GLIBC <= 2.5
CXXABI <= 3.4.8
GLIBCXX <= 3.4.9
GCC <= 4.2.0
이러한 권고는 2016년 1월에 이루어진 관련 논의 [7], [8]의 결과입니다.
아래의 권고에서 pip 또는 PyPI가 이 정책의 세부 사항을 확인하고 강제하려고 해야 한다고 제안하는 것은 아니라는 점에 유의하십시오. 이는 win32와 같은 기존 플랫폼 태그의 세부 사항을 확인하고 강제하지 않는 것과 같습니다. 위의 내용은 (a) 패키지 빌더를 위한 조언이자 (b) 특정 휠이 어떤 시스템에서 작동하지 않을 경우 책임을 배분하는 방법으로 제공됩니다. 정책을 충족한다면 이는 사양 또는 설치 도구의 버그이고, 정책을 충족하지 않는다면 휠의 버그입니다. 이 접근 방식의 유용한 결과 중 하나는 경험을 더 쌓으면서 추가 업데이트와 조정을 진행할 가능성을 열어 둔다는 점입니다. 예를 들어 동일한 시스템을 대상으로 하고 동일한 manylinux1 플랫폼 태그를 사용하는 “manylinux 1.1” 정책을 마련할 수 있습니다. 그러면 pip 또는 PyPI를 추가로 변경할 필요가 없지만, 문제가 있는 것으로 밝혀진 라이브러리를 위 목록에서 제거하거나 안전한 것으로 밝혀진 라이브러리를 추가하도록 목록을 조정할 수 있습니다.
libpythonX.Y.so.1
libpythonX.Y.so.1은 manylinux1 확장이 링크할 수 있는 라이브러리 목록에 포함되지 않는다는 점에 유의하십시오. 거의 모든 경우에 libpythonX.Y.so.1에 명시적으로 링크할 필요는 없습니다. ELF 링크가 작동하는 방식에 따라 인터프리터에 로드된 확장 모듈은 확장 자체가 libpython에 명시적으로 링크되었는지 여부와 관계없이 인터프리터의 모든 심볼에 자동으로 접근할 수 있습니다. 또한 Python이 --enable-shared 없이 빌드되는 일반적인 구성에서는 libpython에 명시적으로 링크하면 문제가 발생합니다. 특히 Debian 및 Ubuntu 시스템에서는 apt install pythonX.Y가 libpythonX.Y.so.1조차 설치하지 않으므로, libpythonX.Y.so.1에 실제로 의존했던 휠은 임포트에 실패할 수 있습니다.
이러한 방식으로 링크된 확장 모듈이 작동하지 않을 수 있는 상황이 한 가지 있습니다. 호스트 프로그램(예: apache2)이 CPython 인터프리터를 포함하는 모듈(예: mod_wsgi)을 로드하기 위해 dlopen()를 사용하고, 호스트 프로그램이 dlopen()에 RTLD_GLOBAL 플래그를 전달하지 않으면, 포함된 CPython은 자체적으로 libpythonX.Y.so.1에 명시적으로 링크하지 않는 확장 모듈을 로드할 수 없습니다. 다행히 apache2는 RTLD_GLOBAL 플래그를 실제로 설정하며, 저희가 찾아낸 embed-CPython-via-a-dlopened-plugin 방식의 다른 모든 프로그램도 마찬가지이므로, 이는 실제로 심각한 문제로 보이지 않습니다. Debian/Ubuntu와의 비호환성은 다소 특수한 예외 상황에서 발생하는 이론적 비호환성보다 더 큰 문제입니다.
이는 manylinux1의 범위를 넘어서는 상당히 복잡하고 미묘한 문제입니다. 자세한 논의는 [9], [10], [11]을 참조하십시오.
UCS-2 대 UCS-4 빌드
CPython 2.x의 모든 버전과 CPython 3.0~3.2를 포함한 버전은 ABI가 호환되지 않는 두 가지 모드로 빌드할 수 있습니다. --enable-unicode=ucs2 구성 플래그를 사용하는 빌드는 유니코드 데이터를 UCS-2 형식(실제로는 UTF-16)으로 저장하는 반면, --enable-unicode=ucs4 구성 플래그를 사용하는 빌드는 유니코드 데이터를 UCS-4로 저장합니다. (CPython 3.3 이상은 항상 UCS-4를 지원하는 다른 저장 방식을 사용합니다.) ucs2 휠이 ucs4 CPython에 설치되거나 그 반대의 일이 발생하지 않도록 하려면 무언가 조치를 취해야 합니다.
이전 버전의 이 PEP에는 이러한 이전 CPython 버전을 대상으로 하는 manylinux1 휠이 항상 ucs4 ABI를 사용해야 한다는 요구사항이 포함되어 있었습니다. 그러나 PEP가 최초로 승인된 시점과 구현된 시점 사이에 pip과 wheel은 관련 CPython 버전에서 이러한 ABI 호환성 측면을 추적하고 검사하는 일급 지원을 얻었으며, 이는 더 나은 해결책입니다. 따라서 이제 manylinux1 플랫폼 태그를 어떤 ABI 태그와도 함께 사용할 수 있도록 허용합니다. 그러나 호환성을 유지하려면 모든 manylinux1 휠에 실질적인 ABI 태그가 포함되도록 하는 것이 매우 중요합니다. 예를 들어 ucs4 CPython에 맞춰 빌드된 휠은 다음과 같은 이름을 가질 수 있습니다.:
PKG-VERSION-cp27-cp27mu-manylinux1_x86_64.whl
^^^^^^ Good!
한편 ucs2 ABI에 맞춰 빌드된 휠은 다음과 같은 이름을 가질 수 있습니다.:
PKG-VERSION-cp27-cp27m-manylinux1_x86_64.whl
^^^^^ Okay!
그러나 다음과 같은 이름의 휠은 절대로 있어서는 안 됩니다.:
PKG-VERSION-cp27-none-manylinux1_x86_64.whl
^^^^ BAD! Don't do this!
이 휠은 둘 다 ucs2 및 ucs4 빌드와 동시에 호환된다고 주장하는데, 이는 좋지 않습니다.
참고로 ucs4 ABI가 Linux CPython 배포자 사이에서 훨씬 더 널리 사용되는 것으로 보입니다.
fpectl 빌드와 fpectl이 없는 빌드
현재 존재하는 모든 CPython 버전은 --with-fpectl 플래그를 configure에 지정하거나 지정하지 않은 상태로 빌드할 수 있습니다. 이 플래그가 CPython ABI를 변경한다는 사실이 밝혀졌습니다. fpectl이 없는 CPython에 맞춰 빌드된 확장은 fpectl이 있는 CPython과는 항상 호환되지만, 그 반대는 반드시 그렇지는 않습니다. (증상: 가져올 때 undefined symbol: PyFPE_jbuf에 대해 불평하는 오류가 발생합니다.) 다음을 참조하십시오: [16].
따라서 최대한의 호환성을 위해 manylinux1 휠을 빌드하는 데 사용하는 CPython은 반드시 without --with-fpectl 플래그로 컴파일해야 하며, manylinux1 확장은 PyFPE_jbuf 심볼을 참조해서는 안 됩니다.
규격을 준수하는 휠의 컴파일
glibc, libgcc 및 libstdc++가 심볼 버전 관리를 처리하는 방식 때문에 실제로 대부분의 개발자가 일상 업무에 사용하는 컴파일러 도구 모음으로는 manylinux1 규격을 준수하는 휠을 빌드할 수 없습니다. 따라서 pip wheel / bdist_wheel의 기본 동작을 변경하려고 시도하지 않습니다. 이 도구들은 계속 일반적인 linux_* 플랫폼 태그를 생성하며, 이를 사용하여 manylinux1 태그가 지정된 휠을 생성하려는 개발자는 두 번째 후처리 단계로 태그를 변경해야 합니다.
manylinux1 표준을 충족하는 휠의 컴파일을 지원하기 위해 두 도구의 초기 초안을 제공합니다.
Docker 이미지
첫 번째 도구는 CentOS 5.11을 기반으로 하는 Docker 이미지이며, manylinux1 휠을 컴파일하기 위한 사용하기 쉬운 독립형 빌드 환경으로 권장됩니다 [12]. 더 최근에 출시된 Linux 배포판에서 컴파일하면 일반적으로 너무 새로운 버전의 심볼에 대한 종속성이 생깁니다. 이 이미지에는 전체 컴파일러 도구 모음(gcc, g++ 및 gfortran 4.8.2)이 설치되어 있을 뿐 아니라 Python과 pip의 최신 릴리스도 포함되어 있습니다.
Auditwheel
두 번째 도구는 auditwheel [13]이라고 하는 명령줄 실행 파일로, 패키지 유지 관리자가 서드파티 외부 종속성을 처리하는 데 도움을 줄 수 있습니다.
위 정책을 충족하는 방식으로 서드파티 외부 라이브러리를 사용하는 휠을 빌드하는 방법은 최소 세 가지입니다.
- 서드파티 라이브러리를 정적으로 링크할 수 있습니다.
- 서드파티 공유 라이브러리를 휠이 의존하는 별도 패키지로 PyPI에서 배포할 수 있습니다.
- 서드파티 공유 라이브러리를 휠 라이브러리 내부에 포함하고 상대 경로로 링크할 수 있습니다.
이 방법들은 모두 유효한 선택지이며 서로 다른 패키지와 커뮤니티에서 효과적으로 사용할 수 있습니다. 정적 링크에는 일반적으로 패키지별 빌드 시스템 수정이 필요하며, PyPI에서 서드파티 종속성을 배포하려면 해당 패키지 사용자 커뮤니티의 조정이 필요할 수 있습니다.
이러한 옵션을 대신하는, 흔히 자동으로 사용할 수 있는 대안으로 auditwheel을 도입합니다. 이 도구는 휠 내부의 모든 ELF 파일을 검사하여 버전이 지정된 심볼 또는 외부 공유 라이브러리에 대한 의존성을 확인하고, manylinux1 정책 준수 여부를 검증합니다. 여기에는 규정을 준수하는 휠에 새 플랫폼 태그를 추가하는 기능도 포함됩니다. 더 중요한 점은 auditwheel이 외부 공유 라이브러리에 의존하는 휠을 자동으로 수정할 수 있다는 것입니다. 즉, 시스템에서 해당 공유 라이브러리를 휠 자체로 복사하고, 런타임에 이러한 라이브러리가 검색되도록 적절한 RPATH 항목을 수정합니다. 이는 빌드 시스템을 변경하지 않고도 라이브러리를 정적으로 링크한 경우와 유사한 결과를 달성합니다. 패키지 제작자는 정적 링크와 마찬가지로 번들링에도 저작권 문제가 수반될 수 있음을 유의해야 합니다.
Linux에서의 번들 휠
manylinux1 휠 내에서 서드파티 라이브러리 의존성을 처리하는 여러 접근 방식이 있음을 인정하지만, manylinux1 정책이 외부 의존성의 번들링을 장려한다는 점을 인식하고 있습니다. 이는 여러 Linux 배포판의 시스템 패키지 관리자들이 따르는 패키지 관리 정책에 반하는 관행입니다 [14], [15]. 이 방식의 주된 목적은 배포판 간 호환성입니다. 또한 PyPI의 manylinux1 휠은 시스템 패키지 관리자를 통해 제공되는 Python 패키지와는 다른 틈새를 차지합니다.
이 PEP에서 일반적인 Linux 배포판의 비번들링 정책에서 벗어나도록 장려하기로 한 결정은 다음과 같은 우려에 근거합니다.
- 자동화된 지속적 통합 및 배포 파이프라인이 사용되는 오늘날에는 새로운 버전을 게시하고 의존성을 업데이트하는 일이 해당 정책이 정의되었을 때보다 쉽습니다.
pip사용자는 미리 빌드된 휠 파일 대신 로컬 빌드를 강제하려는 경우"--no-binary"옵션을 자유롭게 사용할 수 있습니다.- 최신 컨테이너 기반 배포 및 “불변 인프라” 모델의 인기로 인해 애플리케이션 계층에서는 어차피 상당한 번들링이 이루어집니다.
- PyPI를 통한 번들 휠 배포는 현재 Windows와 OS X에서 일반적인 방식입니다.
- 이 PEP는 향후 특정 Linux 배포판을 위한 보다 맞춤화된 바이너리를 제공하는 방안을 배제하지 않습니다.
이 PEP에서 설명하는 모델은 크로스 플랫폼 Python 패키지에 가장 적합합니다. 이미 정적 Windows 및 OS X 휠을 만들기 위해 수행하고 있는 작업의 상당 부분을 재사용할 수 있기 때문입니다. 반면 Linux 전용 패키지에는 이 방식이 덜 적합할 수 있습니다. 이러한 패키지는 Linux 고유의 패키지 관리 기능과 더 긴밀하게 상호 작용하기를 원할 수 있으며, 특정 소수의 배포판만을 대상으로 하는 데 관심이 있기 때문입니다.
보안 관련 사항
Linux에서 중앙 집중식 라이브러리에 의존할 때의 장점 중 하나는 버그 수정과 보안 업데이트를 시스템 전체에 배포할 수 있다는 점입니다. 또한 이러한 라이브러리에 의존하는 애플리케이션은 기반 라이브러리가 업데이트될 때 이러한 패치의 효과를 자동으로 받습니다. 이는 네트워크를 통한 통신이나 암호화와 관련된 패키지의 보안 업데이트에서 특히 중요할 수 있습니다.
OpenSSL과 같은 보안상 중요한 라이브러리를 번들로 포함하여 PyPI를 통해 배포되는 manylinux1 휠은 공개된 취약점 및 패치에 대응하여 신속하게 업데이트할 책임을 지게 됩니다. 이는 Windows에서 바이너리 휠을 배포할 때의 보안상 영향과 밀접하게 유사합니다. Windows는 플랫폼에 시스템 패키지 관리자가 없기 때문에 일반적으로 의존성을 번들로 포함합니다. 특히 OpenSSL은 안정적인 ABI가 없으므로 manylinux1 프로필에 포함할 수 없습니다.
설치 프로그램을 위한 플랫폼 감지
앞에서 wheel이 manylinux1과 호환된다는 것이 무엇을 의미하는지 정의했습니다. 여기서는 Python installation이 manylinux1과 호환된다는 것이 무엇을 의미하는지 설명합니다. 특히 이는 pip과 같은 도구가 설치할 때 manylinux1 태그가 지정된 휠을 고려해야 하는지 여부를 결정하는 데 중요합니다.
manylinux1 프로필은 이미 널리 사용되는 상용 Python 배포판의 수많은 사용자에게 작동하는 것으로 알려져 있으므로, 특별한 이유가 없다면 시스템이 호환된다고 가정하는 방향으로 오류를 처리하도록 설치 도구를 만들 것을 제안합니다.
실제로 발생할 가능성이 높은 잠재적 비호환성의 주요 원인은 네 가지입니다.
- 향후 언젠가는 이 프로필과의 호환성을 깨뜨리는 배포판이 존재할 수 있습니다(예를 들어 프로필에 포함된 라이브러리 중 하나가 하위 호환되지 않는 방식으로 ABI를 변경하는 경우).
- 지나치게 오래된 Linux 배포판(예: RHEL 4)
glibc를 사용하지 않는 리눅스 배포판(예: musllibc를 기반으로 하는 Alpine Linux 또는 Android)
이를 해결하기 위해 두 갈래 접근 방식을 제안합니다. 향후 발생할 수 있는 비호환성에 대응하기 위해, 특정 Python 설치가 manylinux1과 확실히 호환되는지 여부를 Python 배포자가 표시할 수 있는 메커니즘을 표준화합니다. 이는 _manylinux이라는 이름의 모듈을 설치하고 해당 모듈의 manylinux1_compatible속성을 설정하여 수행합니다. 표준 라이브러리에 이러한 모듈을 추가하자는 제안은 하지 않습니다. 이는 배포자와 설치 도구가 만날 수 있도록 정해 둔 잘 알려진 이름일 뿐입니다. 그러나 배포자가 이 모듈을 추가한다면, site-packages/디렉터리가 아니라 표준 라이브러리에 추가해야 합니다. 표준 라이브러리는 가상 환경에 상속되지만(이를 원합니다), site-packages/는 일반적으로 그렇지 않기 때문입니다.
그런 다음 기존 Python 배포판에서 마지막 두 경우를 처리하기 위해, glibc의 존재 여부와 버전을 확인하는 간단하고 신뢰할 수 있는 방법을 제안합니다(기본적으로 배포판의 전반적인 연식을 측정하는 “시계”로 사용하는 것입니다).
구체적으로, 우리가 제안하는 알고리즘은 다음과 같습니다.:
def is_manylinux1_compatible():
# Only Linux, and only x86-64 / i686
from distutils.util import get_platform
if get_platform() not in ["linux-x86_64", "linux-i686"]:
return False
# Check for presence of _manylinux module
try:
import _manylinux
return bool(_manylinux.manylinux1_compatible)
except (ImportError, AttributeError):
# Fall through to heuristic check below
pass
# Check glibc version. CentOS 5 uses glibc 2.5.
return have_compatible_glibc(2, 5)
def have_compatible_glibc(major, minimum_minor):
import ctypes
process_namespace = ctypes.CDLL(None)
try:
gnu_get_libc_version = process_namespace.gnu_get_libc_version
except AttributeError:
# Symbol doesn't exist -> therefore, we are not linked to
# glibc.
return False
# Call gnu_get_libc_version, which returns a string like "2.5".
gnu_get_libc_version.restype = ctypes.c_char_p
version_str = gnu_get_libc_version()
# py2 / py3 compatibility:
if not isinstance(version_str, str):
version_str = version_str.decode("ascii")
# Parse string and check against requested version.
version = [int(piece) for piece in version_str.split(".")]
assert len(version) == 2
if major != version[0]:
return False
if minimum_minor > version[1]:
return False
return True
거부된 대안: 구성 파일(예: /etc/python/compatibility.cfg)을 사용하는 방안도 고려했습니다. 이 방식의 문제는 하나의 파일 시스템에 서로 다른 ABI 프로필을 가진 여러 인터프리터 환경이 포함될 수 있다는 점입니다. 시스템에 설치된 x86_64 CPython의 manylinux1호환성이 사용자 설치 i686 PyPy의 manylinux1호환성에 대해 많은 정보를 알려 주지 못할 수 있습니다. 이 구성 정보를 Python 환경 자체 안에 배치하면 올바른 바이너리에 계속 연결되어 있게 되며, 조회 코드도 크게 단순해집니다.
호환 가능한 것으로 간주해야 하는 모든 플랫폼 태그의 목록과 해당 태그의 선호 순서를 함께 제공하는 등 더 정교한 구조를 사용하는 방안도 고려했습니다. 예를 들면 다음과 같습니다: _binary_compat.compatible = ["manylinux1_x86_64", "centos5_x86_64", "linux_x86_64"]. 그러나 이 방식은 몇 가지 복잡한 문제를 초래합니다. 예를 들어, “manylinux1을 지원하지 않음”(나중에는 manylinux2등일 수 있음)과 “manylinux1을 지원하는지 여부를 명시하지 않음” 상태를 구별할 수 있어야 하는데, 위 표현에서는 이것이 완전히 명확하지 않습니다. 또한 현재 가능한 플랫폼 태그가 manylinux1 및 linux뿐이라는 점을 고려하면, 선호 순서와 관련하여 실제로 어떤 기능이 필요한지도 전혀 분명하지 않습니다. 따라서 Linux에 더 많은 플랫폼 태그가 추가될 때까지 더 완전한 해결책은 별도의 PEP를 위해 미루기로 합니다.
라이브러리 호환성 확인을 위해 커널 버전 확인, manylinux1 프로필에 나열된 각 개별 라이브러리의 검색 및 버전 확인 등 훨씬 더 정교한 검사도 고려했지만, 궁극적으로는 이러한 검사가 사용자에게 실제로 도움이 되기보다 혼란스러운 버그를 일으킬 가능성이 더 높다고 판단했습니다. (예를 들어 배포판마다 이러한 라이브러리를 실제로 배치하는 위치가 다르며, 검사 코드가 올바른 경로 검색을 사용하지 못하면 쉽게 잘못된 결과를 반환할 수 있습니다.)
PyPI 지원
PyPI는 manylinux1 플랫폼 태그를 포함하는 휠의 업로드를 허용해야 합니다. PyPI는 manylinux1 플랫폼 태그를 포함하는 휠이 이 문서에 설명된 manylinux1 정책을 준수하는지 공식적으로 검증하려 해서는 안 됩니다. 이 검증 작업은 별도로 개발되는 auditwheel과 같은 다른 도구에 맡겨야 합니다.
거부된 대안
한 가지 대안은 각 Linux 배포판(및 각 버전)에 대해 별도의 플랫폼 태그를 제공하는 것입니다. 예를 들면 RHEL6, ubuntu14_10, debian_jessie 등이 있습니다. 이 제안은 향후 이러한 플랫폼 태그를 추가하거나, 휠 메타데이터를 더욱 확장하여 휠이 외부 시스템 설치 패키지에 대한 의존성을 선언하도록 하는 가능성을 배제하지 않습니다. 그러나 이러한 확장에는 이 제안보다 훨씬 더 많은 작업이 필요하며, 모든 일반적인 Linux 배포판을 지원하기 위해 여러 빌드 환경을 유지하고 여러 휠을 빌드해야 하는 일을 원하지 않는 패키지 개발자들이 여전히 반기지 않을 수 있습니다. 따라서 이러한 제안은 이 PEP의 범위를 벗어난다고 판단합니다.
향후 업데이트
향후 어느 시점에는 더 현대적인 기준 환경을 지정하는 manylinux2가 등장할 것으로 예상합니다(아마도 CentOS 6을 기반으로 할 것입니다). 언젠가는 manylinux3등이 등장하겠지만, 초기 manylinux1 제안에 대한 경험을 더 쌓을 때까지 이러한 사양의 정의는 미루겠습니다.
참고 자료
Copyright
This document has been placed into the public domain.