[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에 대한 강력한 의존성에서 분리할 수 있게 하는데, 이는 다음 두 가지 주요 이유 때문입니다:

  1. setuptools처럼 보일 필요 없이 훨씬 사용하기 쉬운 새로운 빌드 시스템을 가능하게 합니다.
  2. setuptoolspip을 손상시키지 않고 사용자 인터페이스를 변경할 수 있도록 하여, 더 느슨한 결합을 제공합니다.

pip이 빌드 시스템 자체를 설치할 수 있도록 하는 이 인터페이스는 패키지의 빌드 시점 요구사항(build time requirements)을 설치하는 것도 가능하게 하는데, 이는 pipeasy-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 파일을 읽어서 찾습니다. 이 파일은 빌드 도구를 가져오는 방법과 도구를 호출하기 위해 실행할 명령어의 이름을 설명합니다. 모든 도구는 pipsetuptoolssetup.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에 연결하여 빌드 시스템을 실행하지만, stdoutstderr는 리디렉션되고 사용자(user)와의 통신은 불가능합니다.

일반적인 프로세스와 마찬가지로 0이 아닌 종료 상태는 오류를 나타냅니다.

사용 가능한 포맷 변수 (Available format variables)

  • PYTHON: 사용 중인 Python 인터프리터입니다. 이는 단순히 Python 진입점(entry points)인 것을 호출할 수 있도록 하는 데 중요합니다. 예: {PYTHON} -m foo

사용 가능한 환경 변수 (Available environment variables)

이 변수들은 빌드 시스템의 호출자(caller)에 의해 설정되며 항상 사용할 수 있습니다.

  • PATH: 표준 시스템 경로입니다.
  • PYTHON: 포맷 변수와 동일합니다.
  • PYTHONPATH: 일반적인 Python 메커니즘에 따라 sys.path를 제어하는 데 사용됩니다.

서브커맨드 (Subcommands)

빌드 시스템이 지원해야 하는 몇 가지 개별 서브커맨드가 있습니다. 아래 예시는 설명을 위해 flitbuild_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_DIRwheel이 출력될 기존 디렉토리를 가리킵니다. stdoutstderr는 의미론적 의미가 없습니다. 하나의 파일만 출력되어야 합니다. 만약 더 많은 파일이 출력되면 pip은 임의의 파일을 선택하여 사용합니다. 예시: flit wheel -d /tmp/pip-build_1234
  • develop [--prefix PREFIX]: 프로젝트의 제자리(in-place) ‘개발’ 설치를 수행하기 위한 명령어입니다. stdoutstderr는 의미론적 의미가 없습니다. 모든 빌드 시스템이 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에서 스키마를 업그레이드하는 순서는 다음과 같습니다:

  1. 업데이트된 스키마를 정의하는 새로운 PEP를 발행합니다. 스키마가 완전히 하위 호환되지 않는 경우 새로운 버전 번호를 정의해야 합니다.
  2. 컨슈머(예: pip)는 새로운 스키마 버전에 대한 지원을 구현합니다.
  3. 패키지 작성자는 새로운 스키마 버전에 대한 지원을 도입한 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 metadataflit.ini를 쿼리하고 메타데이터를 wheel METADATA 파일로 마샬링하여 stdout으로 출력할 것입니다.

flit wheelwheel을 출력할 위치를 알려주는 -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.pypip에 의해 ‘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이 사용자 정의 빌드 도구를 호출하는 방법을 배우는 것보다 더 큰 문제로 보입니다.

metadatawheel 명령어는 일관된 메타데이터를 가져야 합니다. 이는 pip이 메타데이터를 읽고 그에 따라 행동한 다음, 결과 wheel이 호환되지 않는 요구사항을 가질 수 있는 레이스 컨디션(race condition)을 피하기 위함입니다. 이 레이스 컨디션은 현재 PEP 426 환경 마커를 사용하는 패키지에서 환경 마커를 지원하지 않는 이전 pip 버전과 함께 작동하기 위해 악용됩니다. 이 PEP에서는 해당 악용이 필요하지 않습니다. setuptools 심이 사용되거나 (이전 pip 버전의 경우) 환경 마커를 지원하는 pip이 사용되기 때문입니다. setuptools 심은 이전 pip 버전이 요구하는 차이점을 악용하는 것을 처리할 수 있습니다.

sdist 동사(verb)를 갖는 것에 대해 논의했습니다. 주된 동기는 빌드 시스템이 pip이 빌드할 수 있는 sdist를 생성할 수 있도록 하는 것이었지만, 이는 순환적입니다. 이 PEP의 전체 요점은 pipsetuptools의 구현을 요구하지 않고도 이러한 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