PEP 516 – pip/conda 등을 위한 빌드 시스템 추상화
- Author:
- Robert Collins <rbtcollins at hp.com>, Nathaniel J. Smith <njs at pobox.com>
- BDFL-Delegate:
- Alyssa Coghlan <ncoghlan at gmail.com>
- Discussions-To:
- Distutils-SIG list
- Status:
- Rejected
- Type:
- Standards Track
- Topic:
- Packaging
- Created:
- 26-Oct-2015
- Resolution:
- Distutils-SIG message
번역·라이선스 안내
이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판
초록
이 PEP는 pip [1] 및 기타 배포 또는 설치 도구가 Python 소스 트리(개발자 트리(예: git 트리)와 소스 배포판 모두)를 다룰 때 사용할 프로그래밍 인터페이스를 명시합니다.
이 프로그래밍 방식 인터페이스는 두 가지 핵심적인 이유로 pip를 현재 setuptools [2]에 대한 강한 의존성에서 분리할 수 있게 합니다:
- setuptools처럼 보일 필요조차 없으면서 훨씬 사용하기 쉬울 수 있는 새로운 빌드 시스템을 가능하게 합니다.
- setuptools 자체가 pip를 망가뜨리지 않고 사용자 인터페이스를 변경할 수 있도록 하여 결합을 느슨하게 합니다.
pip가 빌드 시스템을 설치할 수 있도록 하는 데 필요한 인터페이스는 pip가 패키지의 빌드 시점 요구 사항도 설치할 수 있게 하며, 이는 pip가 easy-install의 설치 구성 요소와 완전한 기능 동등성을 갖추는 데 중요한 단계입니다.
PEP 426은 초안이므로, 그 PEP가 정의한 메타데이터 형식을 활용할 수 없습니다. 하지만 PEP 427 휠은 널리 사용되고 있고 상당히 잘 명시되어 있으므로, 배포 의존성과 일반 프로젝트 메타데이터를 명시하기 위해 그 PEP의 METADATA 형식을 채택했습니다. PEP 508은 의존성을 기술하기 위한 자체 완결적인 언어를 제공하며, 부트스트랩 의존성을 기술하기 위해 이를 얇은 JSON 스키마로 감쌉니다.
PEP 314에 명시된 Python sdist 역시 소스 트리이므로, 이 PEP는 sdist의 정의를 갱신합니다.
PEP 거부
이 PEP에서 제안한 CLI 기반 접근 방식은 PEP 517에서 제안한 Python API 기반 접근 방식을 채택하면서 거부되었습니다. 격리된 하위 프로세스로 실행되는 빌드 백엔드와 통신하는 데 사용되는 구체적인 CLI는 프런트엔드 개발자 도구 구현의 구현 세부 사항으로 간주됩니다.
동기
현재 빌드 시스템과 pip 사이의 잠금(lock-in) 상태 때문에 Python 패키징 생태계에는 오랫동안 쌓여 온 불만이 상당합니다. 그 잠금 상태를 깨는 것은 pip에도, setuptools에도, 그리고 flit [3] 같은 다른 빌드 시스템에도 더 낫습니다.
명세
개요
빌드 도구는 소스 트리의 루트 디렉터리에서 pypa.json 파일을 읽어 찾습니다. 그 파일은 빌드 도구를 얻는 방법과 도구를 호출하기 위해 실행할 명령의 이름을 기술합니다.
모든 도구는 pip가 기존에 setuptools setup.py 인터페이스를 사용하는 방식을 본뜬 단일 명령줄 인터페이스를 따라야 합니다.
pypa.json
pypa.json 파일은 pip 및 소스 트리를 빌드하려는 기타 도구가 설정을 참조할 수 있는 중립적인 설정 파일 역할을 합니다. Python 소스 트리에 pypa.json 파일이 없으면 setuptools 또는 setuptools 호환 빌드 시스템을 의미합니다.
JSON은 다음 스키마를 가집니다. 추가 키는 무시되므로, pypa.json을 다른 관련 도구의 설정 파일로 사용할 수 있습니다. 그렇게 하는 경우 선택한 키는 tools 아래의 네임스페이스에 두어야 합니다:
{"tools": {"flit": ["Flits content here"]}}
- 스키마
- 스키마의 버전입니다. 이 PEP는 버전 “1”을 정의합니다. 없을 경우 기본값은 “1”입니다. 파일을 읽는 모든 도구는 인식할 수 없는 스키마 버전에 대해 오류를 발생시켜야 합니다.
- bootstrap_requires
- 빌드 도구를 실행하기 전에 설치되어 있어야 하는 PEP 508 의존성 명세의 목록이며, 선택 사항입니다. 예를 들어 flit을 사용한다면 요구 사항은 다음과 같을 수 있습니다:
bootstrap_requires: ["flit"]
- build_command
- 필수 키로, 실행할 명령을 기술하는 Python 형식 문자열 [6]의 목록입니다. 예를 들어 flit을 사용한다면 빌드 명령은 다음과 같을 수 있습니다:
build_command: ["flit"]
실행 가능한 모듈인 fred 명령을 사용한다면:
build_command: ["{PYTHON}", "-m", "fred"]
프로세스 인터페이스
실행할 명령은 단순한 Python 형식 문자열 [6]로 정의됩니다.
이를 통해 전용 스크립트를 가진 빌드 시스템과 “python -m somemodule”로 호출되는 빌드 시스템을 모두 지원할 수 있습니다.
프로세스는 현재 작업 디렉터리를 소스 트리의 루트로 설정한 상태에서 실행됩니다.
실행될 때 프로세스는 stdin에서 읽어서는 안 됩니다. 현재 pip는 빌드 시스템의 stdin을 자신의 stdin에 연결한 채로 실행하지만, stdout과 stderr는 리디렉션되므로 사용자와 소통할 수 없습니다.
프로세스에서 늘 그렇듯이, 0이 아닌 종료 상태는 오류를 나타냅니다.
사용 가능한 포맷 변수
- PYTHON
- 사용 중인 Python 인터프리터입니다. 이는 단순히 Python 진입점일 뿐인 것들을 호출할 수 있게 하는 데 중요합니다.{PYTHON} -m foo
사용 가능한 환경 변수
이 변수들은 빌드 시스템의 호출자가 설정하며 항상 사용할 수 있습니다.
- PATH
- 표준 시스템 경로입니다.
- PYTHON
- 포맷 변수와 같습니다.
- PYTHONPATH
- 일반적인 Python 메커니즘에 따라 sys.path를 제어하는 데 사용됩니다.
하위 명령
빌드 시스템이 반드시 지원해야 하는 별개의 하위 명령이 여러 개 있습니다. 아래 예시에서는 설명을 위해 build_command로 flit을 사용합니다.
- build_requires
- 빌드 요구 사항을 조회합니다. 빌드 요구 사항은 UTF-8로 인코딩된 JSON 문서로 반환됩니다. 이 문서에는 PEP 508 의존성 명세의 리스트로 이루어진
build_requires키 하나만 있습니다. 추가 키는 무시해야 합니다. build_requires 명령은 빌드 환경을 구성하지 않고 실행되는 유일한 명령입니다.예시 명령:
flit build_requires
- 메타데이터
- 프로젝트 메타데이터를 조회합니다. stdout에는 UTF-8 인코딩으로 메타데이터만을 출력해야 하며, 그 밖의 것은 출력하지 않아야 합니다. pip은 어떤 다른 패키지를 내려받아 설치해야 하는지 결정하기 위해 metadata를 한 번만 실행합니다. 메타데이터는 PEP 427에 따라 wheel의 METADATA 메타데이터 파일 형식으로 출력됩니다.
metadata 명령이 생성한 메타데이터와 생성된 휠에 들어 있는 메타데이터는 반드시 동일해야 한다는 점에 유의하십시오.
예시 명령:
flit metadata
- wheel -d OUTPUT_DIR
- 프로젝트의 휠을 빌드하기 위해 실행하는 명령입니다. OUTPUT_DIR은 휠을 출력할 기존 디렉터리를 가리킵니다. stdout과 stderr는 의미상 아무런 뜻을 갖지 않습니다. 파일은 하나만 출력해야 합니다. 둘 이상이 출력되면 pip은 그중 임의의 하나를 골라 사용합니다.
예시 명령:
flit wheel -d /tmp/pip-build_1234
- develop [–prefix PREFIX]
- 프로젝트를 제자리(in-place)에 ‘개발’ 설치하는 명령입니다. stdout과 stderr는 의미상 아무런 뜻을 갖지 않습니다.
모든 빌드 시스템이 develop 설치를 수행할 수 있는 것은 아닙니다. 빌드 시스템이 develop 설치를 할 수 없다면, 실행 시 오류를 내야 합니다. 그렇게 하면
pip install -e foo같은 사용자 작업이 실패하게 된다는 점에 유의하십시오.prefix 옵션은 설치에 사용할 대체 접두어를 정의하는 데 사용됩니다. setuptools에는
--root옵션과--user옵션이 있지만, 이들은--prefix를 사용하여 동등하게 처리할 수 있으므로,--root또는--user옵션을 받는 pip이나 기타 도구는 이를 적절히 변환해야 합니다.root 옵션은 명령이 동작할 대체 루트를 정의하는 데 사용됩니다.
예를 들어:
flit develop --root /tmp/ --prefix /usr/local
사용 중인 Python 환경이 sys.prefix를
/usr/로 보고하여/tmp/usr/bin/을 사용하게 되는 경우라도, 스크립트는/tmp/usr/local/bin안에 설치해야 합니다. 패키지 파일 등에도 비슷한 논리가 적용됩니다.
빌드 환경
build_requires 명령을 제외한 모든 명령은 빌드 환경 안에서 실행됩니다. 특정한 구현을 요구하지는 않지만, 빌드 환경은 다음 요구 사항을 충족해야 합니다.
- 프로젝트의 build_requires에 지정된 모든 의존성은
$PYTHON안에서 임포트할 수 있어야 합니다.
- 빌드에 필요한 패키지가 제공하는 모든 명령줄 스크립트는
$PATH에 있어야 합니다.
이에 따른 귀결로, 빌드 시스템은 build_requires로 선언되지 않았거나 Python 표준 라이브러리에 포함되지 않은 Python 패키지에 접근할 수 있다고 가정할 수 없습니다.
격리된(hermetic) 빌드
이 명세는 빌드가 격리되어야 하는지 여부를 규정하지 않습니다. setuptools 같은 기존 빌드 도구는 빌드 시점 요구 사항(예: setuptools_scm)의 설치된 버전을 사용하며, 버전 충돌이 있거나 의존성이 누락된 경우에만 다른 버전을 설치합니다. 그러나 항상 빌드를 격리하고 지정된 의존성만 사용하면 더 나은 일관성을 얻을 수 있을 것입니다.
그러나 여기에는 미묘한 문제가 있습니다. 예를 들어 일부 패키지의 의존성은 충족하지만 문제가 있는 빌드 요구 사항 버전을 사용자가 어떻게 강제로 피할 수 있는지와 같은 문제입니다. 향후 PEP에서 이 문제를 다룰 수 있지만, 현재는 범위에 포함되지 않습니다. 이 문제는 빌드 시스템과 빌드를 수행해야 하는 도구 간의 조정에 필요한 메타데이터에 영향을 주지 않으므로 PEP에서 다룰 사안이 아닙니다.
업그레이드
‘pypa.json’에는 버전이 매겨지므로 호환성을 요구하지 않고도 향후 변경이 가능합니다.
새 PEP에서 두 스키마 중 하나를 업그레이드하는 순서는 다음과 같습니다:
- 갱신된 스키마를 정의하는 새 PEP를 발행합니다. 스키마가 완전히 하위 호환되지 않는다면 새 버전 번호를 정의해야 합니다.
- 소비자(예: pip)가 새 스키마 버전에 대한 지원을 구현합니다.
- 패키지 작성자는 새 스키마 버전 지원을 도입한 ‘pip’(그리고 잠재적으로 다른 소비자) 버전에 대한 의존성을 받아들일 준비가 되었을 때 새 스키마를 선택합니다.
이 PEP의 초기 배포에도 동일한 과정이 적용됩니다. 즉, setuptools shim 없이 이 PEP를 사용할 수 있는 기능이 얼마나 퍼질지는 이를 지원하는 첫 번째 pip 버전의 채택률에 크게 좌우될 것입니다.
sdist의 정적 메타데이터
이 PEP는 현재 sdist의 정적 메타데이터를 신뢰할 수 없다는 문제를 다루지 않습니다. 이는 소스 트리가 sdist에서 왔는지 여부와 관계없이 소스 트리에서 사용 중인 빌드 시스템을 식별하고 사용하는 문제와는 별개의 문제입니다.
컴파일러 옵션 처리
다양한 컴파일러 옵션의 처리는 이 명세의 범위를 벗어납니다.
현재 pip는 setuptools를 실행할 때 사용하는 명령줄에 사용자가 제공한 문자열을 덧붙이는 방식으로 컴파일러 옵션을 처리합니다. 이 방식은 이 PEP에서 정의한 빌드 시스템 인터페이스와 함께 동작하기에 충분합니다. 다만 다양한 빌드 시스템이 발전함에 따라 전역으로 지정된 옵션이 더 이상 전역적으로 동작하지 않게 된다는 예외가 있습니다. 이 문제는 상호 운용성에 영향을 주지 않고 pip(또는 conda나 다른 설치 프로그램)에서 해결할 수 있습니다.
장기적으로는 어떤 컴파일러나 옵션으로 빌드된 휠과 다른 컴파일러나 옵션으로 빌드된 휠의 차이를 휠이 표현할 수 있어야 하며, 이는 PEP에서 다룰 사안입니다.
예시
flit을 사용하기 위한 ‘pypa.json’ 예시:
{"bootstrap_requires": ["flit"],
"build_command": "flit"}
‘pip’는 이 파일을 읽으면 flit을 사용하기 전에 flit이 설치된 환경을 준비할 것입니다.
현재 flit은 setup-requires를 지원하지 않으므로, flit build_requires는 단순히 상수 문자열을 출력할 것입니다:
{"build_requires": []}
flit metadata는 flit.ini를 조회하여 메타데이터를 wheel의 METADATA 메타데이터 파일로 정리하고 이를 stdout에 출력합니다.
flit wheel은 휠을 출력할 위치를 알려 주는 -d 매개변수를 받아야 합니다(pip에 이 기능이 필요합니다).
하위 호환성
이전 버전의 pip는 앞으로도 대체 빌드 시스템을 처리할 수 없습니다. 이는 현 상태보다 나빠지는 것이 아니며, 개별 빌드 시스템 프로젝트는 shim setup.py를 포함할지 여부를 스스로 결정할 수 있습니다.
휠을 생성하고 develop 설치를 수행할 수 있는 기존의 모든 빌드 시스템은 이 추상화 아래에서 실행될 수 있어야 하며, 각 빌드 시스템에 맞는 어댑터를 만들어 PyPI에 게시하기만 하면 됩니다.
pypa.json 파일이 없으면 pip와 같은 도구는 setuptools 빌드 시스템이라고 가정하고 setuptools 명령을 직접 사용해야 합니다.
네트워크 효과
setuptools와 호환되지 않는 빌드 시스템, 즉 setup.py가 없거나 setup.py가 기존 도구가 사용하려는 명령을 받아들이지 않는 빌드 시스템을 채택한 프로젝트는 그러한 기존 도구로 설치할 수 없게 됩니다.
그러한 프로젝트를 다른 프로젝트가 사용하는 경우, 이 효과는 연쇄적으로 퍼집니다.
특히 현재 pip는 setup-requires를 처리하지 않으므로, setuptools와 호환되지 않는 빌드 시스템을 채택한 프로젝트(A)를 아직 pypa.json을 갖추도록 전환하지 않은 두 번째 프로젝트(B)가 setup-requirement로 사용하면, B는 어떤 버전의 pip로도 설치할 수 없게 됩니다. 그 이유는 pip가 ‘setup.py egg_info’를 실행할 때 B의 setup.py가 easy-install을 작동시키고, easy-install이 A를 설치하려다 실패하기 때문입니다.
따라서 현재 setup-requires로 사용되는 도구는 setuptools shim을 반드시 유지하거나, 자신이 전환하기에 앞서 자신을 사용하는 프로젝트를 모두 찾아 pypa.json을 사용하도록 업그레이드하게 할 것을 권장합니다. 현실적으로 후자는 불가능하므로, pbr이나 setuptools_scm 같은 프로젝트와 numpy 같은 프로젝트 모두 setuptools shim을 무기한 유지할 것을 권고합니다.
setuptools shim
겉보기에는 setup.py처럼 보이지만 내부적으로는 pypa.json을 사용하여 빌드를 수행하는 범용 setuptools shim을 작성하는 것도 가능합니다. 이는 pip가 이 시스템을 사용하는 데 필요하지는 않지만, 패키지 작성자가 이전 버전의 pip와의 호환성을 유지하면서도 새로운 기능을 사용할 수 있게 해 줍니다.
근거
이 PEP는 distutils-sig에서 오간 긴 메일링 리스트 스레드에서 시작되었습니다 [4]. 그 후 사람들이 가진 모든 입장을 검토하고 정리하기 위해 온라인 회의가 열렸습니다. 그 회의록은 메일링 리스트에 게시되었습니다 [5].
이 명세는 그 자리에서 도달한 합의를 PEP 형식으로 옮긴 것이며, 남아 있던 사소한 문제들에 대해서는 임의로 내린 몇 가지 선택이 더해져 있습니다.
설계의 기본적인 방침은 추상화와 엄밀하게 관련되지 않은 개발을 요구하지 않으면서 추상화를 도입하는 데 집중하는 것이었습니다. 개선까지의 격차가 작거나 기존 인터페이스를 사용하는 비용이 매우 큰 경우에는 해당 개선을 의존성으로 받아들였지만, 그 밖의 경우에는 향후 반복 작업으로 미루었습니다.
새로운 명세를 정의하는 대신 wheel의 METADATA 메타데이터 파일을 선택한 이유는, pip가 이미 필요한 모든 데이터를 METADATA 메타데이터 파일에 담고 있는 wheel .dist-info 디렉터리를 처리할 수 있기 때문입니다. PEP 426은 아직 초안이므로 사용할 수 없으며, 새로운 메타데이터 형식을 정의하는 일은 해야 할 일이기는 하지만 별개의 문제입니다. 디스크상의 디렉터리를 사용하는 것은 인터페이스에 아무런 가치도 더하지 않습니다(현재 pip는 setuptools CLI의 한계 때문에 그렇게 할 수밖에 없습니다).
‘develop’을 명령으로 사용하는 이유는 ‘setuptools develop’이 하는 일을 수행하는 도구들의 상호 운용성을 규정하는 PEP가 없기 때문입니다. 따라서 pip가 ‘develop’ 단계를 수행하는 책임을 맡으려면 먼저 이를 정의해야 합니다. 그 작업이 끝나면 이 PEP의 후속 PEP를 발표할 수 있습니다.
Python API 대신 명령줄 API를 사용하는 것에는 다소 논란의 여지가 있습니다. 근본적으로는 어떤 방식이든 동작하게 만들 수 있으며, pip 유지 관리자들은 프로세스 기반 인터페이스를 유지하는 것을 강하게 지지해 왔습니다. 이는 현재 pip에서 성숙하고 견고하게 자리 잡은 방식입니다.
파일 형식으로 JSON을 선택한 것은 여러 제약 사이의 타협입니다. 첫째, 표준 라이브러리에는 YAML 인터프리터가 없으며, 그 밖의 부담이 적은 구조화 파일 형식을 위한 인터프리터도 없습니다. 둘째로, INIParser는 여러 이유로 좋지 않은 형식인데, 무엇보다 구조가 매우 빈약하기 때문입니다. 게다가 pip 유지 관리자들도 이 형식을 좋아하지 않습니다. JSON은 표준 라이브러리에 포함되어 있으며, 내장 DSL 없이도 향후 원하는 것을 무엇이든 담을 수 있을 만큼 충분한 구조를 갖추고 있습니다.
Donald는 새로운 것을 고안하는 대신 setup.cfg와 기존 setuptools 명령줄을 사용하자고 제안했습니다. 그렇게 하면 눈에 덜 띄는 변경만으로 상호 운용성을 확보할 수 있지만, pip 쪽에서도 거의 같은 수준의 엔지니어링이 필요합니다. setup.cfg에서 새 키를 찾아야 하고, 빌드를 실행할 설치되지 않은 환경도 구현해야 합니다. 또한 다른 빌드 시스템 작성자들은 setuptools처럼 보이지만 실제로는 상당히 다르게 동작하는 것을 내놓아 사용자를 혼란스럽게 하고 싶어 하지 않으며, 이는 pip이 사용자 정의 빌드 도구를 호출하는 방법을 익히는 것보다 더 큰 문제로 보입니다.
metadata 명령과 wheel 명령은 일관된 메타데이터를 가져야 합니다. 그렇지 않으면 pip이 메타데이터를 읽고 그에 따라 동작한 뒤, 결과로 만들어진 wheel이 호환되지 않는 요구 사항을 가지는 경쟁 조건이 발생할 수 있기 때문입니다. 오늘날 PEP 426 환경 마커를 사용하는 패키지들은 환경 마커를 지원하지 않는 오래된 pip 버전에서도 동작하도록 이 경쟁 조건을 악용합니다. 이 PEP에서는 그러한 악용이 필요하지 않습니다. (오래된 pip 버전에서는) setuptools 심이 사용되거나, 환경 마커를 지원하는 pip이 사용되기 때문입니다. 오래된 pip 버전이 요구하는 차이를 악용하는 일은 setuptools 심이 처리할 수 있습니다.
sdist 동사를 두는 방안을 논의했습니다. 이를 원한 주된 이유는 빌드 시스템이 pip이 빌드할 수 있는 sdist를 생성할 수 있도록 보장하는 것이었습니다. 하지만 이는 순환 논리입니다. 이 PEP의 핵심 목적 자체가 pip이 setuptools 구현 없이도 그러한 sdist나 VCS 소스 트리를 안정적으로 사용할 수 있게 하는 것이기 때문입니다. 기존 소스 트리에서 새 sdist를 생성하는 기능은 현재 pip이 하는 일이 아닙니다. 소스에서 빌드하는 과정의 일부로 이를 수행하는 PR이 있기는 하지만, 논란이 있으며 합의가 이루어지지 않았습니다. 모든 빌드 시스템에 요구 사항을 부과하는 대신 이를 YAGNI로 취급하며, 필요해지면 향후 버전의 인터페이스에 그러한 동사를 추가할 것입니다. sdist에 대한 기존 PEP 314 요구 사항은 여전히 적용되며, distutils나 setuptools 사용자는 setup.py sdist를 사용하여 sdist를 생성할 수 있습니다. 다른 도구는 PEP 314와 호환되는 sdist를 생성해야 합니다. pip 자체는 PEP 314 호환성을 요구하지 않는다는 점에 유의하십시오. pip은 sdist의 메타데이터를 전혀 사용하지 않으며, sdist를 디스크나 버전 관리 시스템에서 가져온 소스 트리처럼 취급합니다.
참고 문헌
Copyright
This document has been placed in the public domain.