PEP 518 – Python 프로젝트를 위한 최소 빌드 시스템 요구 사항 지정
- Author:
- Brett Cannon <brett at python.org>, Nathaniel J. Smith <njs at pobox.com>, Donald Stufft <donald at stufft.io>
- BDFL-Delegate:
- Alyssa Coghlan
- Discussions-To:
- Distutils-SIG list
- Status:
- Final
- Type:
- Standards Track
- Topic:
- Packaging
- Created:
- 10-May-2016
- Post-History:
- 10-May-2016, 11-May-2016, 13-May-2016
- Resolution:
- Distutils-SIG message
번역·라이선스 안내
이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판
초록
이 PEP는 Python 소프트웨어 패키지가 선택한 빌드 시스템을 실행하는 데 필요한 빌드 의존성을 지정하는 방법을 명시합니다. 이 사양의 일부로, 소프트웨어 패키지가 빌드 의존성을 지정하는 데 사용할 새 구성 파일을 도입합니다(향후 구성 세부 정보에도 동일한 구성 파일을 사용할 것을 전제로 합니다).
근거
Python이 프로젝트의 소프트웨어 배포판을 빌드하기 위한 도구를 처음 개발했을 때는 distutils [1]가 선택된 솔루션이었습니다. 시간이 지나면서 setuptools [2]는 distutils 위에 일부 기능을 추가하면서 인기를 얻었습니다. 둘 다 프로젝트 관리자가 소프트웨어 배포판을 빌드하기 위해 실행하는(사용자가 해당 배포판을 설치하기 위해서도 사용하는) setup.py 파일이라는 개념을 사용했습니다.
distutils에서 실행 파일을 사용하여 빌드 요구 사항을 지정하는 것은 문제가 되지 않습니다. distutils가 Python 표준 라이브러리의 일부이기 때문입니다. 빌드 도구가 Python의 일부라는 것은 프로젝트 관리자가 프로젝트의 배포판을 빌드할 때 걱정해야 할 외부 의존성이 setup.py에 없다는 의미입니다. 유일한 의존성이 Python이므로 어떠한 의존성 정보도 지정할 필요가 없었습니다.
그러나 프로젝트가 setuptools를 사용하기로 선택하면 setup.py와 같은 실행 파일의 사용이 문제가 됩니다. 의존성을 알지 못한 채 setup.py 파일을 실행할 수는 없지만, 현재는 해당 정보가 저장된 setup.py 파일을 실행하지 않고는 그 의존성이 무엇인지 자동화된 방식으로 알 수 있는 표준 방법이 없습니다. 이는 파일을 실행하지 않는 한 프로그래밍 방식으로 알 수 없는 자신의 내용을 알아야만 실행할 수 있는 파일이라는 난처한 상황입니다.
Setuptools는 setup() 함수에 setup_requires 인자를 추가하여 이 문제를 해결하려고 했습니다 [3]. 이 솔루션에는 다음과 같은 여러 문제가 있습니다.
setup.py를 실행하지 않고는 setuptools 자체를 제외한 어떤 도구도 이 정보에 접근할 수 없지만, 이러한 항목이 설치되어 있지 않으면setup.py를 실행할 수 없습니다.- setuptools 자체는 여기에 나열된 항목을 설치하지만,
setup()함수가 실행되는 동안에만 설치됩니다. 따라서 여기에 추가된 항목을 실제로 사용하는 유일한 방법은setup()함수 실행 후반까지 이러한 모듈의 가져오기와 사용을 지연하는 점점 더 복잡한 우회 방법을 거치는 것입니다. - 여기에는
setuptools자체를 포함할 수 없으며setuptools를 대체하는 것도 포함할 수 없습니다. 따라서numpy.distutils와 같은 프로젝트는 이를 거의 활용할 수 없고, 사용자가 setuptools 버전을 자연스럽게 더 최신 버전으로 업그레이드할 때까지 프로젝트는 새로운 setuptools 기능을 활용할 수 없습니다. setup_requires에 나열된 항목은setup.py를 실행할 때마다 암묵적으로 설치되지만,setup.py가 실행되는 일반적인 방법 중 하나는 이미 의존성을 관리하고 있는pip와 같은 다른 도구를 통하는 것입니다. 이는pip install spam과 같은 명령이 pip와 setuptools 모두 패키지를 다운로드하고 설치하게 하며, 최종 사용자가 both 도구를 구성해야 하는 상황으로 이어질 수 있음을 의미합니다(또한setuptools의 경우에는 호출을 제어할 수 없는 상태에서, 어느 저장소에서 설치할지와 같은 설정을 변경해야 합니다). 또한 두 도구의 검색 규칙을 모두 알고 있어야 한다는 의미이기도 합니다. 한 도구가 다른 패키지 형식을 지원하거나 최신 버전을 다른 방식으로 결정할 수 있기 때문입니다.
이로 인해 setup_requires의 사용은 드물어졌습니다. 프로젝트는 setup.py 파일 사이에서 코드 일부를 단순히 복사하여 붙여 넣거나, 프로젝트를 빌드하거나 설치하기 전에 사용자가 수동으로 설치해 두기를 기대하는 항목을 다른 곳에 문서화하는 대신 이를 완전히 포기하는 경향이 있습니다.
이 모든 상황으로 인해 pip [4]는 setup.py 파일을 실행할 때 setuptools가 필요하다고 단순히 가정하게 되었습니다. 그러나 이 방식의 문제는 setuptools처럼 다른 프로젝트가 커뮤니티에서 인기를 얻기 시작할 경우 확장되지 않는다는 것입니다. 또한 pip가 setuptools가 아닌 다른 것이 필요하다는 사실을 추론할 수 없을 때 프로젝트에서 이를 사용하려면 마찰이 발생하기 때문에 다른 프로젝트가 인기를 얻는 것도 막습니다.
이 PEP는 특정 파일에 프로젝트 빌드 시스템의 최소 의존성을 선언적인 방식으로 나열하는 방법을 지정하여 이러한 상황을 바로잡으려고 합니다. 이를 통해 프로젝트는 예를 들어 소스 체크아웃에서 휠로 전환하는 데 필요한 빌드 의존성을 나열할 수 있으며, 도구가 프로젝트 자체를 빌드하는 데 무엇이 필요한지 추론할 수 없는 setup.py의 난처한 상황에도 빠지지 않습니다. 이 PEP를 구현하면 프로젝트가 의존하는 빌드 시스템을 사전에 지정할 수 있으므로, pip와 같은 도구가 프로젝트를 빌드하기 위해 빌드 시스템을 실행하는 데 필요한 항목이 설치되어 있는지 확인할 수 있습니다.
이 PEP에 대한 더 많은 맥락과 동기를 제공하기 위해 프로젝트의 빌드 결과물을 생성하는 데 필요한 대략적인 단계를 생각해 보십시오.
- 프로젝트의 소스 체크아웃입니다.
- 빌드 시스템 설치입니다.
- 빌드 시스템을 실행하십시오.
이 PEP는 2단계를 다룹니다. PEP 517은 3단계를 다루며, 빌드 시스템이 작업을 수행하는 데 필요한 추가 의존성을 동적으로 지정하는 방법을 포함합니다. 그러나 이 PEP의 목적은 빌드 시스템이 단순히 실행을 시작하는 데 필요한 최소 요구 사항을 지정하는 것입니다.
사양
파일 형식
빌드 시스템 의존성은 TOML 형식으로 작성된 pyproject.toml이라는 이름의 파일에 저장됩니다 [5].
이 형식은 사람이 사용하기 쉽고(JSON과 달리 [6]), 충분히 유연하며(configparser와 달리 [8]), 표준에서 비롯되었고(configparser와도 달리 [8]), 지나치게 복잡하지 않기 때문에(YAML과 달리 [7]) 선택되었습니다. TOML 형식은 이미 Rust 커뮤니티에서 Cargo 패키지 관리자 [12]의 일부로 사용되고 있으며, 비공개 이메일에서 Rust 커뮤니티가 TOML을 선택한 것에 상당히 만족한다고 밝혔습니다. 다양한 대안이 선택되지 않은 이유에 대한 보다 자세한 논의는 Other file formats 섹션에서 확인할 수 있습니다. 그러나 작성자들도 구성 파일 형식의 선택은 궁극적으로 주관적이며 하나를 선택해야 했다는 점을 알고 있으며, 이 상황에는 TOML이 적합하다고 생각합니다.
아래에는 도구가 인식하거나 준수할 것으로 예상되는 테이블을 나열합니다. 이 PEP에서 지정하지 않은 테이블은 다른 PEP에서 향후 사용하도록 예약되어 있습니다.
빌드 시스템 테이블
[build-system] 테이블은 빌드 관련 데이터를 저장하는 데 사용됩니다. 처음에는 테이블에서 하나의 키만 유효하며 필수입니다. 바로 requires입니다. 이 키의 값은 빌드 시스템을 실행하는 데 필요한 PEP 508 의존성을 나타내는 문자열 목록이어야 합니다(현재는 setup.py 파일을 실행하는 데 필요한 의존성을 의미합니다).
setuptools에 의존하는 대부분의 Python 프로젝트에서 pyproject.toml 파일은 다음과 같습니다.:
[build-system]
# Minimum requirements for the build system to execute.
requires = ["setuptools"] # PEP 508 specifications.
현재 커뮤니티에서 setuptools의 사용이 매우 널리 퍼져 있으므로, pyproject.toml 파일이 없을 때 빌드 도구는 위의 예시 구성 파일을 기본 동작으로 사용해야 합니다.
도구는 [build-system] 테이블의 존재를 요구해서는 안 됩니다. pyproject.toml 파일은 빌드 관련 데이터 이외의 구성 세부 정보를 저장하는 데 사용될 수도 있으므로, [build-system] 테이블이 없어도 정당합니다. 파일이 존재하지만 [build-system]테이블이 없다면 위에 지정된 기본값을 사용해야 합니다. 테이블이 지정되었지만 필수 필드가 누락된 경우 도구는 이를 오류로 간주해야 합니다.
도구 테이블
[tool]테이블에서는 빌드 도구뿐 아니라 Python 프로젝트와 관련된 모든 도구가 [tool] 내의 하위 테이블을 사용하는 한 사용자가 구성 데이터를 지정할 수 있습니다. 예를 들어 flit 도구는 [tool.flit]에 구성을 저장합니다.
서로 다른 프로젝트가 동일한 하위 테이블을 사용하여 충돌을 일으키지 않도록 tool.* 네임스페이스 내에서 이름을 할당하는 메커니즘이 필요합니다. 프로젝트가 tool.$NAME 하위 테이블을 사용할 수 있는 경우는 Cheeseshop/PyPI에서 $NAME항목을 소유한 경우에 한합니다.
JSON 스키마
TOML 파일에서 생성되는 데이터의 타입별 표현을 단지 예시로 제공하기 위해, 다음 JSON Schema [13]가 데이터 형식과 일치합니다.:
{
"$schema": "http://json-schema.org/schema#",
"type": "object",
"additionalProperties": false,
"properties": {
"build-system": {
"type": "object",
"additionalProperties": false,
"properties": {
"requires": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": ["requires"]
},
"tool": {
"type": "object"
}
}
}
거부된 아이디어
의미 체계 버전 키
구성 파일 구조를 미래에도 사용할 수 있도록 하기 위해 semantics-version키가 처음에 제안되었습니다. 1을 기본값으로 하여, 이전에 정의된 키나 테이블의 의미가 변경되고 하위 호환성이 깨지는 경우 semantics-version을 새로운 숫자로 증가시키자는 아이디어였습니다.
결국 이것은 성급한 최적화라고 결정되었습니다. 구성 파일에서 의미상 미리 정의된 내용에 대한 변경은 상당히 보수적일 것으로 예상합니다. 또한 하위 호환성을 깨뜨리는 변경이 발생했을 경우에는 이전 도구를 중단시키지 않도록 새로운 의미론에 다른 이름을 사용할 수 있습니다.
더 중첩된 네임스페이스
이 PEP의 초기 초안에는 최상위 [package] 테이블이 있었습니다. 이 아이디어는 의미론 버전 관리 체계에 일정한 범위 지정을 적용하려는 것이었습니다(이 아이디어가 거부된 이유는 A semantic version key를 참조하십시오). 범위 지정의 필요성이 사라지면서 최상위 테이블을 두는 것은 불필요해졌습니다.
기타 테이블 이름
[build-system] 테이블에 대해 제안된 또 다른 이름은 [build]였습니다. 대체 이름은 더 짧지만, 테이블에 저장되는 정보의 의도를 충분히 전달하지 못합니다. distutils-sig 메일링 리스트에서 투표한 결과 현재 이름이 채택되었습니다.
기타 파일 형식
여러 다른 파일 형식이 검토를 위해 제안되었지만, 다양한 이유로 모두 거부되었습니다. 주요 요구 사항은 사람이 형식을 편집할 수 있어야 하며 프로젝트가 구현을 쉽게 벤더링할 수 있어야 한다는 것이었습니다. 이로 인해 XML과 같이 사람이 사용하기에 친화적이지 않고 진지하게 논의된 적도 없는 특정 형식은 처음부터 제외되었습니다.
검토한 파일 형식의 개요
검토한 다른 대안들을 거부한 주요 이유는 다음 절에 요약되어 있으며, 전체 검토 내용(TOML을 지지하는 긍정적인 주장 포함)은 [14]에서 확인할 수 있습니다.
TOML은 우리가 관심을 둔 모든 기능을 제공하면서도 대안들이 초래하는 단점을 피했기 때문에 최종적으로 선택되었습니다.
| 기능 | TOML | YAML | JSON | CFG/INI |
|---|---|---|---|---|
| 잘 정의됨 | 예 | 예 | 예 | |
| 실제 데이터 형식 | 예 | 예 | 예 | |
| 신뢰할 수 있는 유니코드 | 예 | 예 | 예 | |
| 신뢰할 수 있는 주석 | 예 | 예 | ||
| 사람이 쉽게 편집할 수 있음 | 예 | ?? | ?? | |
| 도구로 쉽게 편집할 수 있음 | 예 | ?? | 예 | ?? |
| 표준 라이브러리에 있음 | 예 | 예 | ||
| pip에서 쉽게 벤더링할 수 있음 | 예 | 해당 없음 | 해당 없음 |
(표에서 “??”는 대부분의 사람이 “예”라고 답하고 싶어 할 항목을 나타내지만, 명확한 사양이 없거나 기본 파일 형식 사양이 놀라울 정도로 복잡하기 때문에 실제로는 많은 특이 사항과 엣지 케이스가 발생합니다)
pytoml TOML 파서는 순수 Python 코드 약 300줄로 구성되어 있으므로, 표준 라이브러리 외부에 있다는 점은 이에 크게 불리하게 작용하지 않았습니다.
Python 리터럴도 잠재적인 형식으로 논의되었지만, 파일 형식 검토에서는 고려되지 않았습니다(일반적으로 기존에 존재하는 파일 형식이 아니기 때문입니다).
JSON
JSON 형식 [6]은 처음에 고려되었지만 곧 거부되었습니다. 사람이 읽을 수 있고 문자열 기반인 데이터 교환 형식으로는 훌륭하지만, 사람이 쉽게 편집하기에는 구문이 적합하지 않습니다(예를 들어 주석을 허용하지 않으면서도 구문이 필요 이상으로 장황합니다).
제안된 데이터의 JSON 파일 예는 다음과 같습니다.:
{
"build": {
"requires": [
"setuptools",
"wheel>=0.27"
]
}
}
YAML
YAML 형식 [7]은 손으로 더 쉽게 다룰 수 있으면서 JSON [6]의 상위 집합이 되도록 설계되었습니다. YAML에는 세 가지 주요 문제가 있습니다.
하나는 사양이 방대하다는 점입니다. 레터 크기 용지로 인쇄하면 86페이지에 달합니다. 따라서 한 파서에서는 작동하지만 다른 파서에서는 작동하지 않는 YAML 기능을 누군가 사용할 가능성이 있습니다. 일부 집합을 표준으로 정하자는 제안이 있었지만, 이는 기본적으로 이 파일에만 해당하는 새로운 표준을 만드는 것을 의미하며 장기적으로 실행 가능하지 않습니다.
둘째는 YAML 자체가 기본적으로 안전하지 않다는 점입니다. 이 사양은 구성 데이터를 다룰 때 피하는 것이 가장 좋은 임의 코드 실행을 허용합니다. 물론 이러한 동작을 피할 수는 있습니다. 예를 들어 PyYAML은 safe_load 연산을 제공합니다. 하지만 어떤 도구가 부주의하게 load를 대신 사용하면 임의 코드 실행에 노출됩니다. 이 PEP는 본질적으로 코드 실행을 수반하는 프로젝트 구축에 초점을 맞추고 있지만, 프로젝트 이름과 버전 번호 같은 다른 구성 데이터가 언젠가 임의 코드 실행이 바람직하지 않은 동일한 파일에 포함될 수도 있습니다.
마지막으로 YAML의 가장 널리 사용되는 Python 구현은 PyYAML [9]이며, 수천 줄의 코드와 선택적 C 확장 모듈로 이루어진 대규모 프로젝트입니다. 그 자체로는 반드시 문제가 되는 것은 아니지만, 이는 pip와 같은 프로젝트에서 더욱 문제가 됩니다. 이러한 프로젝트는 완전히 독립적으로 동작하기 위해 PyYAML을 종속성으로 벤더링해야 할 가능성이 높기 때문입니다(그렇지 않으면 설치 도구가 작동하려면 설치 도구가 필요한 상황이 됩니다). 더 단순한 버전의 라이브러리를 잠재적으로 벤더링하는 것이 얼마나 쉬운지 확인하기 위해 PyYAML을 재작업한 개념 증명이 수행되었으며, 이는 그러한 가능성이 있음을 보여줍니다.
YAML 파일의 예는 다음과 같습니다.:
build:
requires:
- setuptools
- wheel>=0.27
configparser
configparser [8]가 받아들이는 내용을 기반으로 한 INI 스타일 구성 파일이 고려되었습니다. 안타깝게도 configparser가 받아들이는 내용에 대한 사양이 없어서 버전 간 지원 편차가 발생합니다. 예를 들어 Python 2.7의 ConfigParser가 받아들이는 내용은 Python 3의 configparser가 받아들이는 내용과 동일하지 않습니다. Python 3가 받아들이는 내용을 표준으로 정하고 configparser 모듈의 백포트를 단순히 벤더링할 수도 있지만, 그렇게 하려면 이 PEP에서 지정한 메타데이터를 사용하려는 모든 프로젝트가 configparser의 백포트를 사용해야 한다고 이 PEP에 명문화해야 합니다. 이는 지나치게 제한적이며, 특정 버전의 configparser가 필요하다는 사실을 누군가 알지 못하면 혼란을 초래할 수 있습니다.
INI 파일의 예는 다음과 같습니다.:
[build]
requires =
setuptools
wheel>=0.27
Python 리터럴
누군가는 Python 리터럴을 구성 형식으로 사용하자고 제안했습니다. 파일의 최상위 수준에는 하나의 딕셔너리가 포함되고, 모든 데이터는 해당 딕셔너리 안에 들어가며, 섹션은 키로 정의됩니다. 모든 Python 프로그래머에게 익숙한 형식이고, 구성 데이터를 읽기 위한 서드파티 종속성이 암묵적으로 필요하지 않으며, ast.literal_eval() [11]로 파싱하면 안전할 수 있습니다. Python 리터럴은 JSON과 동일할 수 있으며, 후행 쉼표와 주석을 지원한다는 추가적인 이점이 있습니다. 또한 Python의 더 풍부한 데이터 모델은 향후 일부 구성 요구 사항에 유용할 수 있습니다(예: 문자열이 아닌 딕셔너리 키, 부동 소수점 값과 정수 값의 구분).
반면 Python 리터럴은 Python 전용 형식이며, 이러한 데이터를 Python으로 작성되지 않은 패키징 도구 등에서 읽어야 할 수도 있다고 예상됩니다.
제안된 데이터에 대한 Python 리터럴 파일의 예는 다음과 같습니다.:
# The build configuration
{"build": {"requires": ["setuptools",
"wheel>=0.27", # note the trailing comma
# "numpy>=1.10" # a commented out data line
]
# and here is an arbitrary comment.
}
}
setup.cfg를 계속 사용하기
setuptools가 일반 형식으로 사용하는 setup.cfg에는 두 가지 문제가 있습니다. 하나는 위의 configparser 논의에서 언급했듯이 .ini 파일이라는 점입니다. 다른 하나는 해당 파일의 스키마가 엄격하게 정의된 적이 없으므로, setuptools 설치를 잠재적으로 혼란스럽게 만들지 않고 앞으로 어떤 형식을 안전하게 사용할 수 있을지 알 수 없다는 점입니다.
기타 파일 이름
여러 다른 파일 이름이 고려되었지만 거부되었습니다(이는 매우 전형적인 bikeshedding 주제이므로, 결정은 대부분 취향에 달려 있습니다).
- pysettings.toml
- 가장 합리적인 대안입니다.
- pypa.toml
- PyPA [10]를 참조하는 것은 의미가 있지만, 이는 다소 틈새적인 용어입니다. 도메인별 지식이 없어도 파일 이름이 의미를 갖도록 하는 것이 더 좋습니다.
- pybuild.toml
- 이 PEP의 제한적인 관점에서는 이 파일 이름이 의미가 있지만, 빌드와 관련 없는 메타데이터가 파일에 추가되기라도 하면 이름은 더 이상 의미가 없게 됩니다.
- pip.toml
- 특정 도구에 지나치게 한정적입니다.
- meta.toml
- 너무 일반적입니다. 프로젝트가 자체 메타데이터 파일을 원할 수 있습니다.
- setup.toml
- 전통적으로
setup.py를 따르지만, 파일이 향후 포함할 수 있는 내용과 반드시 일치하지는 않습니다(예를 들어 프로젝트 이름을 아는 것이 본질적으로 해당 설정의 일부입니까?). - pymeta.toml
- 프로그래밍 및/또는 Python을 처음 접하는 사람에게는 분명하지 않습니다.
- pypackage.toml & pypackaging.toml
- “package”가 무엇인지(프로젝트인지 네임스페이스인지)에 대한 이름의 혼동이 있습니다.
- pydevelop.toml
- 파일에는 개발에만 국한되지 않는 세부 정보가 포함될 수 있습니다.
- pysource.toml
- 소스 코드와 직접적인 관련이 없습니다.
- pytools.toml
- 이 파일은 (현재) 프로젝트 관리를 대상으로 하므로 오해의 소지가 있습니다.
- dstufft.toml
- 특정 인물에 지나치게 한정적입니다. ;)
참고 자료
Copyright
This document has been placed in the public domain.