[Rejected] PEP 516 - Build system abstraction for pip/conda etc
원문 링크: PEP 516 - Build system abstraction for pip/conda etc
상태: Rejected 유형: Standards Track 작성일: 26-Oct-2015
PEP 516 – pip/conda 등을 위한 빌드 시스템 추상화
요약 (Abstract)
이 PEP는 pip
및 기타 배포 또는 설치 도구들이 Python 소스 트리(개발자 트리 또는 소스 배포판(sdist))와 작업할 때 사용할 프로그래밍 인터페이스를 명세합니다. 이 프로그래밍 인터페이스는 pip
을 현재 setuptools
에 대한 강력한 의존성에서 분리할 수 있게 하는데, 이는 다음 두 가지 주요 이유 때문입니다:
setuptools
처럼 보일 필요 없이 훨씬 사용하기 쉬운 새로운 빌드 시스템을 가능하게 합니다.setuptools
가pip
을 손상시키지 않고 사용자 인터페이스를 변경할 수 있도록 하여, 더 느슨한 결합을 제공합니다.
pip
이 빌드 시스템 자체를 설치할 수 있도록 하는 이 인터페이스는 패키지의 빌드 시점 요구사항(build time requirements)을 설치하는 것도 가능하게 하는데, 이는 pip
이 easy-install
의 설치 구성 요소와 완전히 동등한 기능을 갖추는 데 중요한 단계입니다.
PEP 426은 아직 초안(draft)이므로 해당 메타데이터 형식을 사용할 수 없습니다. 하지만 PEP 427 wheel
은 널리 사용되고 잘 명세되어 있으므로, 배포 의존성 및 일반 프로젝트 메타데이터를 지정하기 위해 해당 METADATA
형식을 채택했습니다. PEP 508은 의존성을 설명하는 자체 포함된 언어를 제공하며, 이 언어를 얇은 JSON 스키마로 캡슐화하여 부트스트랩 의존성(bootstrap dependencies)을 설명합니다.
PEP 314에 명세된 Python sdist
또한 소스 트리이므로, 이 PEP는 sdist
의 정의를 업데이트합니다.
PEP 거절 (PEP Rejection)
이 PEP에서 제안된 CLI(Command Line Interface) 기반 접근 방식은 PEP 517에서 제안된 Python API 기반 접근 방식이 선호되어 거절되었습니다. 격리된 서브프로세스로 실행되는 빌드 백엔드(build backends)와 통신하는 데 사용되는 특정 CLI는 프론트엔드 개발 도구 구현의 세부 사항으로 간주될 것입니다.
동기 (Motivation)
Python 패키징 생태계에서는 빌드 시스템과 pip
간의 현재의 고착(lock-in)에 대한 상당한 불만이 누적되어 있습니다. 이러한 고착을 해소하는 것은 pip
, setuptools
, 그리고 flit
과 같은 다른 빌드 시스템 모두에게 더 좋습니다.
명세 (Specification)
개요 (Overview)
빌드 도구는 소스 트리의 루트 디렉토리에서 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"]}}
schema
: 스키마의 버전입니다. 이 PEP는 버전 “1”을 정의합니다. 생략 시 “1”이 기본값입니다. 파일을 읽는 모든 도구는 인식할 수 없는 스키마 버전에 대해 오류를 발생시켜야 합니다.bootstrap_requires
: 빌드 도구를 실행하기 전에 설치해야 하는 PEP 508 의존성 명세의 선택적 목록입니다. 예를 들어,flit
을 사용하는 경우 요구사항은 다음과 같을 수 있습니다:["flit"]
build_command
: 필수 키입니다. 실행할 명령어를 설명하는 Python 포맷 문자열의 목록입니다. 예를 들어,flit
을 사용하는 경우 빌드 명령어는["flit"]
일 수 있습니다. 만약fred
라는 실행 가능한 모듈인 명령어를 사용하는 경우:["{PYTHON}", "-m", "fred"]
프로세스 인터페이스 (Process interface)
실행할 명령어는 간단한 Python 포맷 문자열로 정의됩니다. 이는 전용 스크립트를 가진 빌드 시스템과 “python -m somemodule
“을 사용하여 호출되는 빌드 시스템을 모두 허용합니다.
프로세스는 소스 트리의 루트를 현재 작업 디렉토리로 설정하여 실행됩니다. 실행 시, 프로세스는 stdin
에서 읽어서는 안 됩니다. pip
은 현재 stdin
을 자체 stdin
에 연결하여 빌드 시스템을 실행하지만, stdout
과 stderr
는 리디렉션되고 사용자(user)와의 통신은 불가능합니다.
일반적인 프로세스와 마찬가지로 0이 아닌 종료 상태는 오류를 나타냅니다.
사용 가능한 포맷 변수 (Available format variables)
PYTHON
: 사용 중인 Python 인터프리터입니다. 이는 단순히 Python 진입점(entry points)인 것을 호출할 수 있도록 하는 데 중요합니다. 예:{PYTHON} -m foo
사용 가능한 환경 변수 (Available environment variables)
이 변수들은 빌드 시스템의 호출자(caller)에 의해 설정되며 항상 사용할 수 있습니다.
PATH
: 표준 시스템 경로입니다.PYTHON
: 포맷 변수와 동일합니다.PYTHONPATH
: 일반적인 Python 메커니즘에 따라sys.path
를 제어하는 데 사용됩니다.
서브커맨드 (Subcommands)
빌드 시스템이 지원해야 하는 몇 가지 개별 서브커맨드가 있습니다. 아래 예시는 설명을 위해 flit
의 build_command
를 사용합니다.
build_requires
: 빌드 요구사항을 쿼리합니다. 빌드 요구사항은 PEP 508 의존성 명세 목록으로 구성된build_requires
라는 하나의 키를 가진 UTF-8 인코딩 JSON 문서로 반환됩니다. 추가 키는 무시해야 합니다.build_requires
명령어는 빌드 환경을 설정하지 않고 실행되는 유일한 명령어입니다. 예시:flit build_requires
metadata
: 프로젝트 메타데이터를 쿼리합니다. 메타데이터만stdout
으로 UTF-8 인코딩으로 출력되어야 합니다.pip
은 다른 패키지를 다운로드하고 설치해야 할지 결정하기 위해metadata
를 한 번만 실행합니다. 메타데이터는 PEP 427에 따른wheel METADATA
파일로 출력됩니다.metadata
명령어로 생성된 메타데이터와 생성된wheel
에 포함된 메타데이터는 동일해야 합니다. 예시:flit metadata
wheel -d OUTPUT_DIR
: 프로젝트의wheel
을 빌드하기 위해 실행할 명령어입니다.OUTPUT_DIR
은wheel
이 출력될 기존 디렉토리를 가리킵니다.stdout
및stderr
는 의미론적 의미가 없습니다. 하나의 파일만 출력되어야 합니다. 만약 더 많은 파일이 출력되면pip
은 임의의 파일을 선택하여 사용합니다. 예시:flit wheel -d /tmp/pip-build_1234
develop [--prefix PREFIX]
: 프로젝트의 제자리(in-place) ‘개발’ 설치를 수행하기 위한 명령어입니다.stdout
및stderr
는 의미론적 의미가 없습니다. 모든 빌드 시스템이develop
설치를 수행할 수 있는 것은 아닙니다. 빌드 시스템이develop
설치를 수행할 수 없는 경우 실행 시 오류를 발생시켜야 합니다. 이렇게 하면pip install -e foo
와 같은 작업이 실패하게 됩니다.prefix
옵션은 설치를 위한 대체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
내에 스크립트를 설치해야 합니다. 패키지 파일 등에도 유사한 로직이 적용됩니다.
빌드 환경 (The build environment)
build_requires
명령어를 제외하고, 모든 명령어는 빌드 환경 내에서 실행됩니다. 특정 구현은 요구되지 않지만, 빌드 환경은 다음 요구사항을 달성해야 합니다.
- 프로젝트의
build_requires
에 명시된 모든 의존성은$PYTHON
내에서 임포트(import)할 수 있어야 합니다. - 빌드에 필요한 패키지에서 제공하는 모든 명령줄 스크립트는
$PATH
에 존재해야 합니다.
이는 빌드 시스템이 build_requires
로 선언되지 않았거나 Python 표준 라이브러리에 없는 어떠한 Python 패키지에 대한 접근도 가정할 수 없다는 것을 의미합니다.
밀봉된 빌드 (Hermetic builds)
이 명세는 빌드가 밀봉(hermetic)되어야 하는지 여부를 규정하지 않습니다. setuptools
와 같은 기존 빌드 도구는 빌드 시점 요구사항(예: setuptools_scm
)의 설치된 버전을 사용하며, 버전 충돌이나 누락된 의존성 시에만 다른 버전을 설치합니다. 그러나 항상 빌드를 격리하고 지정된 의존성만 사용하는 것이 더 나은 일관성을 생성할 수 있습니다.
그러나, 사용자가 일부 패키지 의존성을 충족하는 빌드 요구사항의 잘못된 버전을 강제로 피하는 방법과 같은 미묘한 문제가 있습니다. 미래의 PEP에서 이 문제를 다룰 수 있지만, 현재 범위에는 포함되지 않습니다. 이는 빌드 시스템과 빌드를 수행해야 하는 것들 간의 조정을 위해 필요한 메타데이터에 영향을 미치지 않으므로 PEP 자료가 아닙니다.
업그레이드 (Upgrades)
pypa.json
은 호환성 요구 없이 미래 변경을 허용하도록 버전이 지정됩니다.
새로운 PEP에서 스키마를 업그레이드하는 순서는 다음과 같습니다:
- 업데이트된 스키마를 정의하는 새로운 PEP를 발행합니다. 스키마가 완전히 하위 호환되지 않는 경우 새로운 버전 번호를 정의해야 합니다.
- 컨슈머(예:
pip
)는 새로운 스키마 버전에 대한 지원을 구현합니다. - 패키지 작성자는 새로운 스키마 버전에 대한 지원을 도입한
pip
버전(및 잠재적으로 다른 컨슈머)에 대한 의존성을 도입해도 괜찮을 때 새로운 스키마를 선택합니다.
이 PEP의 초기 배포도 동일한 과정을 거칠 것입니다. setuptools
심(shim) 없이 이 PEP를 사용할 수 있는 기능의 전파는 주로 이를 지원하는 pip
의 첫 번째 버전의 채택률에 따라 결정될 것입니다.
sdist
의 정적 메타데이터 (Static metadata in sdists)
이 PEP는 sdist
의 정적 메타데이터를 신뢰할 수 없는 현재의 문제를 다루지 않습니다. 이는 소스 트리에서 사용되는 빌드 시스템을 식별하고 사용하는 것과는 별개의 문제입니다.
컴파일러 옵션 처리 (Handling of compiler options)
다른 컴파일러 옵션의 처리는 이 명세의 범위를 벗어납니다.
pip
은 현재 setuptools
를 실행할 때 명령줄에 사용자 제공 문자열을 추가하여 컴파일러 옵션을 처리합니다. 이 접근 방식은 이 PEP에서 정의된 빌드 시스템 인터페이스와 함께 작동하기에 충분하지만, 전역적으로 지정된 옵션은 다른 빌드 시스템이 발전함에 따라 전역적으로 작동하지 않게 됩니다. 이 문제는 pip
(또는 conda
또는 다른 설치 프로그램)에서 상호 운용성에 영향을 미치지 않고 해결할 수 있습니다.
장기적으로 wheel
은 한 컴파일러 또는 옵션으로 빌드된 wheel
과 다른 wheel
간의 차이를 표현할 수 있어야 하며, 이는 PEP 자료입니다.
예시 (Examples)
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
은 wheel
을 출력할 위치를 알려주는 -d
매개변수를 받아야 합니다 (pip
에 필요합니다).
하위 호환성 (Backwards Compatibility)
오래된 pip
버전은 대체 빌드 시스템을 처리할 수 없을 것입니다. 이는 현상 유지와 다르지 않으며, 개별 빌드 시스템 프로젝트는 setup.py
심(shim)을 포함할지 여부를 결정할 수 있습니다.
wheel
을 생성하고 develop
설치를 수행할 수 있는 모든 기존 빌드 시스템은 이 추상화 하에서 실행될 수 있어야 하며, PyPI
에 특정 어댑터를 구축하고 게시하기만 하면 됩니다.
pypa.json
파일이 없는 경우, pip
과 같은 도구는 setuptools
빌드 시스템을 가정하고 setuptools
명령어를 직접 사용해야 합니다.
네트워크 효과 (Network effects)
setuptools
와 호환되지 않는 빌드 시스템을 채택하는 프로젝트(즉, setup.py
가 없거나, setup.py
가 기존 도구가 사용하려는 명령어를 허용하지 않는 경우)는 기존 도구에 의해 설치될 수 없을 것입니다.
이러한 프로젝트가 다른 프로젝트에 의해 사용될 경우, 이 효과는 계단식으로 발생할 것입니다.
특히 pip
이 현재 setup-requires
를 처리하지 않기 때문에, setuptools
와 호환되지 않는 빌드 시스템을 채택하고 (A) 두 번째 프로젝트 (B)가 pypa.json
으로 전환하지 않은 상태에서 setup-requirement
로 사용되는 모든 프로젝트 (A)는 어떤 pip
버전에서도 B를 설치할 수 없게 만들 것입니다. 이는 B의 setup.py
가 pip
에 의해 ‘setup.py egg_info
‘가 실행될 때 easy-install
을 트리거하고, 그것이 A를 설치하려다 실패할 것이기 때문입니다.
따라서 현재 setup-requires
로 사용되는 도구는 setuptools
심을 유지하거나, 소비자를 찾아 pypa.json
사용으로 미리 업그레이드하도록 하는 것을 권장합니다. 현실적으로 이는 불가능하므로, pbr
, setuptools_scm
과 같은 프로젝트와 numpy
와 같은 프로젝트 모두에 대해 setuptools
심을 무기한으로 유지하는 것이 좋습니다.
setuptools
심 (setuptools shim)
setup.py
처럼 보이고 내부적으로 pypa.json
을 사용하여 빌드를 구동하는 일반적인 setuptools
심을 작성하는 것이 가능할 것입니다. 이는 pip
이 시스템을 사용하는 데 필요하지 않지만, 패키지 작성자가 새 기능을 사용하면서도 이전 pip
버전과의 호환성을 유지할 수 있도록 할 것입니다.
근거 (Rationale)
이 PEP는 distutils-sig
의 긴 메일링 리스트 스레드에서 시작되었습니다. 그 후 온라인 회의가 열려 사람들이 가지고 있던 모든 입장을 디버깅했습니다. 회의록은 목록에 게시되었습니다.
이 명세는 거기서 도달한 합의를 PEP 형식으로 번역한 것이며, 남아있는 사소한 질문에 대한 몇 가지 임의의 선택이 포함되어 있습니다.
설계의 기본 휴리스틱은 추상화에 엄격하게 묶이지 않은 개발을 요구하지 않고 추상화를 도입하는 데 중점을 두는 것이었습니다. 개선과의 격차가 작거나 기존 인터페이스를 사용하는 비용이 매우 높은 경우, 개선을 의존성으로 받아들였지만, 그렇지 않은 경우 미래의 반복으로 미루었습니다.
새로운 명세를 정의하는 대신 wheel METADATA
파일을 선택한 이유는 pip
이 이미 METADATA
파일에 필요한 모든 데이터를 인코딩하는 wheel .dist-info
디렉토리를 처리할 수 있기 때문입니다. PEP 426은 아직 초안이므로 사용할 수 없으며, 새로운 메타데이터 형식을 정의하는 것은 별개의 문제입니다. 디스크에 디렉토리를 사용하는 것은 인터페이스에 어떠한 가치도 추가하지 않을 것입니다 (setuptools CLI
의 제한으로 인해 pip
이 현재 그렇게 해야 합니다).
develop
을 명령어로 사용하는 이유는 ‘setuptools develop
‘이 하는 일의 상호 운용성을 명세하는 PEP가 없기 때문입니다. 따라서 pip
이 ‘develop
’ 단계를 수행할 책임을 맡기 전에 이를 정의해야 할 것입니다. 일단 그렇게 되면 이 PEP의 후속 PEP를 발행할 수 있을 것입니다.
Python API 대신 명령줄 API를 사용하는 것은 다소 논란의 여지가 있습니다. 근본적으로 모든 것이 작동하도록 만들 수 있으며, pip
유지보수자는 현재 pip
에서 성숙하고 견고한 프로세스 기반 인터페이스를 유지하는 것을 강력히 지지해왔습니다.
파일 형식으로 JSON을 선택한 것은 여러 제약 조건 간의 타협입니다. 첫째, YAML
인터프리터나 다른 마찰이 적은 구조화된 파일 형식에 대한 stdlib
가 없습니다. 둘째, INIParser
는 여러 이유로 좋지 않은 형식인데, 주로 구조가 매우 최소한이기 때문입니다. 하지만 pip
의 유지보수자들은 INIParser
를 좋아하지 않습니다. JSON은 stdlib
에 있으며, 미래에 원하는 모든 것을 내장 DSL 없이도 포함할 수 있는 충분한 구조를 가지고 있습니다.
Donald는 새로운 것을 발명하는 대신 setup.cfg
와 기존 setuptools
명령줄을 사용할 것을 제안했습니다. 이는 덜 가시적인 변경으로 상호 운용성을 허용할 수 있지만, pip
측면에서는 거의 동일한 엔지니어링 작업이 필요합니다. (setup.cfg
에서 새 키를 찾고, 빌드를 실행할 비설치 환경을 구현하는 것) 그리고 다른 빌드 시스템 작성자들이 setuptools
와 비슷하지만 다르게 작동하는 것을 제공하여 사용자들을 혼란스럽게 하고 싶지 않다는 바람은 pip
이 사용자 정의 빌드 도구를 호출하는 방법을 배우는 것보다 더 큰 문제로 보입니다.
metadata
및 wheel
명령어는 일관된 메타데이터를 가져야 합니다. 이는 pip
이 메타데이터를 읽고 그에 따라 행동한 다음, 결과 wheel
이 호환되지 않는 요구사항을 가질 수 있는 레이스 컨디션(race condition)을 피하기 위함입니다. 이 레이스 컨디션은 현재 PEP 426 환경 마커를 사용하는 패키지에서 환경 마커를 지원하지 않는 이전 pip
버전과 함께 작동하기 위해 악용됩니다. 이 PEP에서는 해당 악용이 필요하지 않습니다. setuptools
심이 사용되거나 (이전 pip
버전의 경우) 환경 마커를 지원하는 pip
이 사용되기 때문입니다. setuptools
심은 이전 pip
버전이 요구하는 차이점을 악용하는 것을 처리할 수 있습니다.
sdist
동사(verb)를 갖는 것에 대해 논의했습니다. 주된 동기는 빌드 시스템이 pip
이 빌드할 수 있는 sdist
를 생성할 수 있도록 하는 것이었지만, 이는 순환적입니다. 이 PEP의 전체 요점은 pip
이 setuptools
의 구현을 요구하지 않고도 이러한 sdist
또는 VCS 소스 트리를 안정적으로 사용할 수 있도록 하는 것입니다. 기존 소스 트리에서 새로운 sdist
를 생성하는 것은 pip
이 현재 수행하는 작업이 아니며, 소스에서 빌드하는 과정의 일부로 이를 수행하는 PR이 있지만 논란의 여지가 있고 합의가 부족합니다. 모든 빌드 시스템에 요구사항을 부과하기보다는, 이를 YAGNI(You Aren’t Gonna Need It)로 취급하고, 필요한 경우 인터페이스의 미래 버전에 이러한 동사를 추가할 것입니다. sdist
에 대한 기존 PEP 314 요구사항은 여전히 적용되며, distutils
또는 setuptools
사용자는 setup.py sdist
를 사용하여 sdist
를 생성할 수 있습니다. 다른 도구는 PEP 314와 호환되는 sdist
를 생성해야 합니다. pip
자체는 PEP 314 호환성을 요구하지 않습니다. sdist
의 메타데이터를 사용하지 않으며, 디스크 또는 버전 관리의 소스 트리처럼 취급됩니다.
⚠️ 알림: 이 문서는 AI를 활용하여 번역되었으며, 기술적 정확성을 보장하지 않습니다. 정확한 내용은 반드시 원문을 확인하시기 바랍니다.
Comments