파이썬 패키징 – 과거, 현재, 미래

본 문서는 https://bernat.tech/posts/pep-517-518/ 글을 옮긴 것입니다(오역이 있을 수 있습니다).

pip install을 실행하면 정확히 어떤 일이 일어나는지 궁금해 본 적이 있나요? 이 게시물에서는 이전의 설치 단계와 PEP-517 및 PEP-518의 채택으로 인해 모든 단계가 어떻게 변경되는지에 대해 자세히 설명합니다.

이전 게시물에서 소스 트리, 소스 배포 및 휠의 세 가지 컨텐츠 설치 방법에 대해 설명했습니다. 마지막 두 가지 유형만 중앙 Python 리포지토리인 PyPI에 업로드됩니다. 그러나 (예를 들어, pip에 git 프로토콜을 추가하면) 소스 트리를 손에 넣을 수 있습니다. wheel이 다른 방식보다 나은 점은 사용자 컴퓨터에서 빌드 작업을 수행할 필요가 없다는 것입니다. wheel은 다운로드하여 압축을 푸는 것입니다.

파이썬 패키지 빌드하기

이제 빌드가 발생하는 위치(사용자 또는 개발자 컴퓨터)에 관계없이 패키지를 빌드해야 합니다(sdist 또는 wheel). 그러기 위해선 빌더가 필요합니다. 역사적으로 타사 패키지의 필요성은 일찍부터 드러났습니다. Python 1.6과 함께 2000년에 Python이 배터리를 포함한다는 원칙에 따라 distutils 패키지가 Python 표준 라이브러리에 추가되었습니다. 빌드 논리가 포함된 setup.py 파일의 개념을 도입했으며 python setup.py cmd를 통해 트리거됩니다.

사용자는 코드를 라이브러리로 패키징할 수 있었지만, 종속성 자동 설치와 같은 기능은 없었다. 또한, 개선 라이프사이클은 코어 인터프리터 릴리즈 사이클과 직접 연관되어 있었습니다. 2004년에 setuptools 도구가 만들어지고, distutils 도구 위에 구축되며, 다른 우수한 기능으로 확장되었습니다. 그것은 순식간에 널리 퍼져서 대부분의 파이썬 설치는 코어 인터프리터 자체와 함께 setuptools을 제공하기 시작했다.

옛날에는 모든 패키지가 소스 배포판이었습니다. Wheel를 사용한 배포는 훨씬 뒤인 2014년에 이루어졌습니다. distutils는 매우 숙련된 소수의 사람들만이 패키징을 할 때 만들어졌습니다. 매우 유연하고 필수적입니다. 패키지 생성 프로세스의 모든 단계를 수정하기 위해 python 스크립트를 작성합니다.

하지만 이것의 단점은 배우고 이해하는 것이 결코 쉽지 않다는 것이입니다. 이는 Python의 인기가 높아지고 Python의 내부 작업에 능숙하지 못한 사용자가 늘어남에 따라 점점 더 이슈가 되었습니다.

빌드 요구 사항

pip로 소스 배포를 설치하기 위해 대부분 다음의 명령을 수행했습니다.

  1. 패키지 발견
  2. 소스 배포판을 다운로드하고 압축을 풉니다
  3. 압축을 푼 폴더에서 python setup.py install 을 실행합니다.(빌드 + 설치 수행)

개발자는 배포판을 생성하기 위해 python setup.py sdist를 수행하고 중앙 저장소에 업로드하기 위해 python setup.py upload를 수행했습니다(upload 명령은 2013년부터 비보안 HTTP 연결을 사용한 업로드로 인해 더 이상 사용되지 않으며 Twine 도구를 선호하여 업로드 명령도 최종 사용자가 실제 업로드 전에 생성된 패키지를 검사할 수 없도록 했습니다).

pip가 python setup.py install을 실행할 때 패키지를 설치할 때 python 인터프리터를 사용하여 실행하였다. 따라서 빌드 작업은 해당 인터프리터 내에서 이미 사용할 수 있는 모든 타사 패키지에 액세스할 수 있었습니다. 특히 호스트 python 인터프리터에 설치된 setuptools 도구 버전을 정확히 사용했습니다. 패키지가 현재 설치된 버전보다 최신 릴리스에서 사용할 수 있는 setuptools 도구 기능을 사용한 경우 설치를 완료할 수 있는 유일한 방법은 설치된 setuptools 도구를 먼저 업데이트하는 것이었습니다.

새 릴리스에 다른 패키지를 손상시키는 버그가 포함된 경우 잠재적으로 문제가 발생할 수 있습니다. 특히 설치된 패키지를 사용자가 변경할 수 없는 시스템에서 문제가 발생합니다. 그런 다음 빌더(예: setuptools)가 cython과 같은 다른 도우미 패키지를 사용하려고 할 때 발생하는 문제도 있었습니다.

이러한 도움말 중 하나라도 누락된 경우 일반적으로 패키지 import 실패 오류와 함께 빌드가 중단되었습니다.

      File "setup_build.py", line 99, in run
        from Cython.Build import cythonize
    ImportError: No module named Cython.Build

개발자 측에서 이러한 빌드 종속성을 제공할 방법이 없었습니다. 또한 사용자가 런타임에 사용하지 않을 경우에도 모든 패키징 빌드 종속성을 설치해야 함을 의미했습니다. 이 문제를 해결하기 위해 PEP-518이 만들어졌습니다.

PEP 518의 아이디어는 호스트 python과 함께 현재 설치된 패키지를 사용해 빌드하는 대신 패키지는 빌드 작업에 필요한 사항을 명시할 수 있는 기능을 제공한다는 개념입니다. 이것을 호스트 파이썬에서 사용할 수 있도록 하는 대신 패키징 명령을 실행하기 위해 격리된 파이썬 환경(virtualenv)을 만듭니다.

이제 python setup.py install이 다음과 같이 됩니다.

  1. 임시 폴더 생성
  2. 격리된 python 환경을 생성합니다(서드 파티의 site-packages에서). python -m virtualenv our_build_env, 이 python 실행 파일을 python_isolated로 참조하겠습니다.
  3. 빌드 종속성 설치
  4. python_isolated setup.py bdist_wheel을 통해 설치할 수 있는 휠 생성
  5. python의 사이트 패키지로 wheel을 추출합니다.

이것으로 우리는 런타임 파이썬 환경 내에 실제로 cython을 설치하지 않고도 cython에 의존하는 패키지를 설치할 수 있습니다. 빌드 종속성을 지정하는 파일 및 방법은 pyproject.toml 메타데이터 파일입니다.

개발자 머신에서 소스 배포 또는 wheel을 생성할 때도 동일한 메커니즘을 사용할 수 있습니다. pip wheel . --no-deps 명령은 빌드 시스템의 종속성을 충족하는 격리된 파이썬을 자동 생성하고 해당 환경 내에서 python setup.py bdist_wheel 또는 python setup.py sdist 명령을 호출합니다.

패키징 도구의 다양성

그런데 여기에 문제가 하나 더 있습니다. 이러한 모든 작업은 여전히 setup.py의 실행이라고 하는 20년 전에 도입된 메커니즘을 거칩니다. 전체 생태계는 여전히 이전 버전과의 호환성을 유지하기 위해 많은 것을 변경할 수 없는 distutils 및 setuptools 인터페이스를 기반으로 구축됩니다.

또한 패키징하는 동안 임의의 사용자 측 Python 코드를 실행하는 것은 위험하므로 경험이 적은 사용자가 디버그하기 어려운 미묘한 오류로 이어집니다. 명령형 빌드 시스템은 20년 전 우리가 모든 사용 사례를 인식하지 못했을 때 유연성에 탁월했습니다. 그럼에도 불구하고 이제 우리가 잘 이해하고 있으므로 아마도 다양한 사용 사례에 대해 매우 강력하고 쉬운 패키지 빌더를 만들 수 있을 것입니다.

Paul Ganssle(setuptoolsdateutil의 유지 관리자)의 말을 인용하자면:

이상적으로 기본 옵션은 99%의 경우에 잘 작동하는 선언적 빌드 구성이며 유연성이 필요할 때 명령형 시스템으로 폴백할 수 있는 옵션이 있습니다. 이 시점에서 명령형 빌드 옵션을 사용해야 하는 경우 코드 냄새가 나는 세계로 이동할 수 있습니다.

setup.py의 가장 큰 문제는 대부분의 사람들이 선언적으로 사용하고 명령적으로 사용할 때 빌드 시스템에 버그를 도입하는 경향이 있다는 것입니다. 이에 대한 한 가지 예: Python 2.7 전용 종속성이 있는 경우 setup.py에서 sys.version을 사용하여 조건부로 종속성을 지정하고 싶을 수 있지만 sys.version은 빌드를 실행한 인터프리터만 참조합니다. 대신 설치 요구 사항에 대해 선언적 환경 마커를 사용해야 합니다.

flit은 2015년에 도입하면서 이미 이 가정이 옳았다는 것을 증명했습니다. 이는 Python을 처음 접하는 많은 사람들이 가장 좋아하는 패키징 도구가 되었으며, 새로운 사용자가 많은 총기를 피할 수 있도록 합니다. 그러나 이 지점에 도달하기 위해 flitdistutils/setuptools 위에 다시 빌드해야 하므로 구현이 간단하지 않고 코드베이스가 상당히 많은 shim 계층이 많습니다(예를 들어 소스 배포에 대한 setup.py를 계속 생성합니다).

이제 이러한 족쇄에서 해방되고 다른 사람들이 자신의 사용 사례에 맞게 쉽게 패키징할 수 있는 패키징 도구를 빌드하도록 권장하여 setup.py를 기본값이 아닌 예외로 만들 때입니다. setuptools는 이를 선도하는 유일한 사용자 인터페이스인 setup.cfg를 제공할 계획이며, PEP-517 시스템이 설치된 경우 대부분의 경우 setup.py를 남용하는 것을 피해야 합니다. 모든 것을 setuptools 및 distutils에 다시 연결하지 않고 새 빌드 백엔드 생성을 용이하게 하기 위해 PEP-517이 생성되었습니다. 빌더를 백엔드와 프론트엔드로 분리합니다. 프론트엔드는 선언된 모든 빌드 종속성을 충족하는 격리된 파이썬 환경을 제공합니다. 백엔드는 프론트엔드가 소스 배포 또는 wheel을 생성하기 위해 격리된 환경에서 호출할 수 있는 후크를 제공합니다.

또한 setup.py 파일 및 해당 명령을 통해 백엔드와 대화하는 대신 Python 모듈 및 함수로 이동합니다. 모든 패키징 백엔드는 최소한 두 가지 메서드인 build_wheelbuild_sdist를 구현하는 python 개체 API를 제공해야 합니다. API 객체 포인트는 pyproject.toml 파일 안래의 build-backend 키를 통해 지정됩니다.

[build-system]
requires = ["flit"]
build-backend = "flit.api:main"

위의 코드는 프론트엔드에 대해 격리된 파이썬 환경 내에서 위의 코드를 실행하여 백엔드를 확보할 수 있음을 의미합니다.

import flit.api
backend = flit.api.main

# build wheel via
backend.build_wheel()

# build source distribution via
backend.build_sdist()

공식 API를 어디에서 어떻게 노출할 것인지는 백엔드에 달려 있습니다.

  1. flit은 flit.buildapi를 통해 수행합니다.
  2. setuptools는 setuptools.build_meta(나중에 읽어야 하는 이유)에서 두 가지 변형을 제공합니다.
  3. poetry는 poetry.masonry.api를 통해 수행합니다.

이를 통해 프론트엔드에서 distutils의 레거시 결정에 더 이상 바인딩되지 않는 패키징 도구를 가질 수 있습니다.

tox와 패키징

tox는 테스트 도구이며 대부분의 프로젝트에서 주어진 패키지의 여러 Python 인터프리터 버전과의 호환성을 보장하기 위해 사용합니다. 또한 검사 중인 패키지가 설치된 Python 환경을 빠르게 생성할 수 있으므로 오류를 더 빠르게 재현할 수 있습니다.

패키지를 테스트하려면 먼저 소스 배포를 빌드해야 합니다. PEP-518과 PEP-517은 모두 상황을 더 좋게 만들지만 일부 사용 사례에서는 이 기능을 켜면 패키징이 깨질 수 있습니다. 따라서 버전 3.3.0에서 tox가 격리된 빌드를 추가했을 때 현재로서는 기본적으로 활성화하지 않기로 결정했습니다. 수동으로 활성화해야 합니다(올해 말인 2021년에 발표될 버전 4에서 기본적으로 켜짐).

적절한 requiresbuild-backend가 있는 pyproject.toml을 지정했으면 tox.ini 내에서 isolated_build 플래그를 켜야 합니다.

[tox]
isolated_build = True

그 후 패키징 단계의 tox는 소스 배포를 빌드합니다(PEP-518에 따라 격리된 Python 환경에 빌드 종속성을 제공함). 이후 PEP-517에 명시된 대로 빌드 백엔드를 호출합니다. 그렇지 않으면 tox는 tox가 설치된 동일한 인터프리터로 python setup.py sdist 명령을 호출하여 소스 배포를 빌드하는 이전 방법을 사용합니다.

결론

Python Packaging Authority는 이 모든 것이 합리적이고 사용자 친화적이고 오류가 없으며 안정적인 빌드를 갖기를 바랍니다. 이 표준에 대한 사양은 2015년과 2017년 사이에 긴 스레드로 작성되고 토론되었습니다. 제안은 가장 많은 혜택을 받을 만큼 충분히 좋은 것으로 간주되었지만 일부 덜 주류 사용 사례는 간과될 수 있었습니다.

귀하의 사용 사례가 그러한 경우 필요하다고 생각되는 경우 PEP가 언제든지 향상될 수 있다는 점에 대해 걱정하지 마십시오. 이 시리즈의 다음 게시물에서는 이 두 PEP를 출시하는 동안 커뮤니티에서 부딪힌 몇 가지 문제점을 살펴보겠습니다. 이것들은 배운 교훈으로 작용해야 하며 아직 해야 할 일이 있다는 것을 보여주어야 합니다. 아직 모든 것이 완벽하지는 않습니다. 그러나 우리는 점점 나아지고 있습니다. 도울 수 있다면 패키징 커뮤니티에 가입하고 함께 더 나은 환경을 만들어 갑시다!

답글 남기기

아래 항목을 채우거나 오른쪽 아이콘 중 하나를 클릭하여 로그 인 하세요:

WordPress.com 로고

WordPress.com의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

Google photo

Google의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

Twitter 사진

Twitter의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

Facebook 사진

Facebook의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

%s에 연결하는 중