Following system colour scheme Selected dark colour scheme Selected light colour scheme

Python 개선 제안 한국어 번역

PEP 804 – 외부 의존성 레지스트리와 이름 매핑 메커니즘

Author:
Pradyun Gedam <pradyunsg at gmail.com>, Ralf Gommers <ralf.gommers at gmail.com>, Michał Górny <mgorny at quansight.com>, Jaime Rodríguez-Guerra <jaime.rogue at gmail.com>, Michael Sarahan <msarahan at gmail.com>
Discussions-To:
Discourse thread
Status:
Draft
Type:
Standards Track
Topic:
Packaging
Requires:
725
Created:
03-Sep-2025
Post-History:
22-Sep-2025

Table of Contents

번역·라이선스 안내

이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain or CC0-1.0, whichever is more permissive 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판

초록

이 PEP는 패키징 도구가 외부 의존성 식별자(PEP 725에서 도입됨)를 다른 패키지 저장소의 대응 항목에 매핑할 수 있게 하는 이름 매핑 메커니즘을 명시합니다.

동기

PyPI의 패키지는 PyPI에 없는 빌드 시점 및 런타임 의존성을 필요로 하는 경우가 많습니다. PEP 725는 이러한 의존성을 표현하기 위한 메타데이터를 도입했습니다. Python 패키지에 구체적인 외부 의존성 메타데이터를 사용하려면 주어진 의존성 식별자를 다른 생태계에서 사용하는 지정자에 매핑해야 하며, 이를 통해 다음이 가능해집니다.

  • 도구가 외부 의존성을 다른 패키징 저장소/생태계의 패키지에 자동으로 매핑할 수 있게 하는 것,
  • Python 패키지 설치 프로그램과 빌드 프런트엔드가 출력하는 오류 메시지에 필요한 외부 의존성을 사용자 시스템의 관련 시스템 패키지 관리자가 사용하는 패키지 이름으로 포함하고, 사용자가 해당 패키지의 설치 안내를 얻을 수 있도록 하는 것.

Linux 배포판, conda, Homebrew, Spack, Nix 같은 패키징 생태계는 Python 패키지의 전체 의존성 집합이 필요하며, 업스트림 Python 패키지에서 제공하는 메타데이터로부터 의존성 정보를 자동으로 생성하려는 pyp2rpm (Fedora), Grayskull (conda), dh_python (Debian) 같은 도구를 갖추고 있습니다. PEP 725 이전에는 pyproject.toml이나 다른 어떤 표준 메타데이터 파일에도 이를 위한 메타데이터가 없었기 때문에 외부 의존성을 수동으로 처리했습니다. 이를 자동으로 변환할 수 있게 하는 것은 이 PEP의 핵심 이점이며, Python 패키징을 더 쉽고 안정적으로 만들어 줍니다. 또한 저자들은 다른 종류의 도구도 이 정보를 활용할 것으로 예상합니다. 예를 들어 Repology, Dependabot, libraries.io 같은 의존성 분석 도구가 있습니다.

근거

선행 사례

R 언어에는 외부 의존성 메타데이터를 apt-get 같은 패키지 관리자의 설치 명령으로 변환하는 방법을 아는 중앙 레지스트리를 갖춘 R 패키지 시스템 요구 사항이 있습니다. 이 레지스트리는 여러 Linux 배포판과 Windows에 대한 매핑을 한곳에 모아 관리합니다. macOS는 포함되어 있지 않습니다. README의 “Rule Coverage”는 이 시스템이 CRAN의 패키지를 소스에서 빌드할 때의 성공 확률을 높인다는 것을 보여 주었습니다. 전체 CRAN 패키지를 기준으로 Ubuntu 18은 78.1%에서 95.8%로, CentOS 7은 77.8%에서 93.7%로, openSUSE 15.0은 78.2%에서 89.7%로 개선되었습니다. 성공 확률은 레지스트리가 얼마나 잘 유지 관리되는지에 달려 있지만, 그 효과는 상당합니다. Docker 컨테이너 안의 Ubuntu와 CentOS에서 빌드에 실패하는 패키지가 약 4분의 1로 줄어듭니다.

Fedora 같은 RPM 기반 배포판은 pyp2rpm의 규칙 기반 구현 (NameConvertor)을 사용할 수 있습니다. 주된 규칙은 PyPI 패키지의 RPM 이름이 일반적으로 f"python3-{pypi_package_name}"이라는 것입니다. 드문 예외로는 주로 애플리케이션을 배포하여 접두사를 생략하는 패키지(예: Black 포매터는 python3-black이 아니라 그냥 black입니다)와 서로 다른 Python 버전을 위한 변형(예: RHEL 9에서 setuptools는 Python 3.9용 python3-setuptools로 찾을 수 있지만, python3.11-setuptools와 python3.12-setuptools도 제공됩니다)이 있습니다. 자세한 내용은 Fedora의 Python 패키징 지침에서 확인할 수 있습니다.

Debian 패키지는 일반적으로 f"python3-{import_name}" 명명 체계를 따르지만, 몇 가지 예외가 있습니다. 일부 하위 커뮤니티는 중간 삽입어를 사용하며(예: Django 패키지는 f"python3-django-*" 아래에 들어갑니다), 애플리케이션은 python3- 접두사 없이 이름 그대로 배포되는 경우가 많습니다. 추가 세부 사항은 Debian의 Python 정책에서 확인할 수 있습니다.

Gentoo는 dev-python/ 카테고리와 몇 가지 잘 명시된 규칙을 사용하여 Python 패키지 이름을 짓는 비슷한 접근 방식을 따릅니다.

Conda-forge는 더 명시적인 이름 매핑을 갖추고 있습니다. 기본 이름은 conda-forge와 PyPI에서 같지만(예: numpy는 numpy에 매핑됩니다), 이름 충돌과 이름 변경 때문에 예외가 많기 때문입니다(예: PyTorch의 PyPI 이름은 torch이지만 conda-forge에서는 pytorch입니다). 여러 팀이 각각 유지 관리하는 이름 매핑 작업이 여러 개 있습니다. Conda-forge의 인프라는 regro/cf-graph-countyfair에서 매핑 하나를 생성합니다. Grayskull은 자체적으로 선별한 매핑을 유지 관리합니다. Prefix.dev는 자사 도구에서 conda와 PyPI 통합을 지원하기 위해 parselmouth 매핑을 만들었습니다. 이들의 접근 방식과 장단점에 대한 보다 완전한 개요는 conda/grayskull#564에서 찾을 수 있습니다.

OpenStack 생태계에서도 매핑 작업을 해야 합니다. 이들은 모두 오로지 Linux 배포판만을 대상으로 합니다. pkg-map은 diskimage-builder와 함께 제공되며, 사용자가 임의의 변수 이름과 대상 배포판(Red Hat, Debian, OpenSUSE 등)에서 그에 대응하는 이름을 정의하는 파일 형식을 제공합니다. PyYAML 예제를 참고하십시오. bindep은 bindep.txt 파일(예제 참고)을 정의하며, 사용자는 이 파일에 PyPI에서 설치할 수 없는 의존성을 적어 둘 수 있습니다. 이 형식은 줄 단위이며, 각 줄에는 Debian 생태계에서 쓰는 이름으로 의존성을 하나씩 적습니다. 다른 배포판에 대해서는 대괄호 안에 쓰는 “필터” 문법을 제공하며, 사용자는 이를 통해 다른 대상 플랫폼, 선택적 의존성, 엑스트라를 지정할 수 있습니다.

매핑은 SageMath 같은 다른 생태계에서도 필요하며, PyPI 패키지를 자신이 선택한 시스템 패키지 관리자로 설치하려는 최종 사용자에게도 필요합니다(StackOverflow 질문 예시).

이름 매핑의 거버넌스와 유지 관리 비용

수많은 패키징 생태계에 대한 외부 의존성 매핑을 유지 관리하려면 비용이 많이 들 수 있습니다. 그래서 레지스트리를 다음과 같은 방식으로 정의합니다:

  • 중앙 기관이 인정된 DepURL 목록과 알려진 생태계 매핑을 관리합니다.
  • 매핑 자체는 각 대상 패키징 생태계가 관리합니다.

따라서 각 생태계는 이 시스템에 참여할지를 스스로 선택하며(옵트인), 관련 유지 관리 비용은 여러 생태계에 분산됩니다.

패키지 관리자별 설치 명령 생성

외부 의존성이 있는 Python 패키지의 작성자는 보통 문서에 그 외부 의존성의 설치 방법을 적어 둡니다. 이런 설치 방법은 작성하기도, 최신 상태로 유지하기도 어렵고, 보통 플랫폼 하나나 기껏해야 몇 개만 다룹니다. 예를 들어, 다음은 SciPy가 외부 빌드 의존성(C/C++/Fortran 컴파일러, OpenBLAS, pkg-config)에 대해 제공하는 설치 방법입니다:

  • Debian/Ubuntu: sudo apt install -y gcc g++ gfortran libopenblas-dev liblapack-dev pkg-config python3-pip python3-dev
  • Fedora/CentOS/RHEL: sudo dnf install gcc-gfortran python3-devel openblas-devel lapack-devel pkgconfig
  • Arch Linux: sudo pacman -S gcc-fortran openblas pkgconf
  • macOS의 Homebrew: brew install gfortran openblas pkg-config

패키지 이름은 배포판마다 크게 다르며, 어떤 배포판은 헤더와 그 밖의 빌드 시점 의존성을 별도의 -dev/-devel 패키지로 분리하고 어떤 배포판은 분리하지 않는 등의 차이도 있습니다. 이 PEP의 레지스트리가 있으면, “모든 외부 의존성에 대해 이 생태계가 선호하는 패키지 관리자의 설치 명령 표시”라는 의미의 도구 명령으로 이러한 설치 방법을 더 포괄적이면서도 유지 관리하기 쉽게 만들 수 있습니다. 이는 독립 실행형 도구로 구현할 수도 있고, 임의의 Python 개발 워크플로 도구(예: Pip, Poetry, Hatch, PDM, uv)의 새 하위 명령으로 구현할 수도 있습니다.

이를 위해 각 생태계 매핑은 호환된다고 알려진 패키지 관리자 목록을 제공할 수 있으며, 여기에 패키지를 설치하는 방법과 설치된 패키지를 조회하는 방법을 템플릿 형태로 함께 담을 수 있습니다. 제공되는 설치 명령 템플릿은 조회 명령 템플릿과 짝을 이루므로, 도구는 설치를 시도하지 않고도 필요한 패키지가 이미 있는지 확인할 수 있습니다(설치 작업은 비용이 클 수 있고, 버전 업그레이드 같은 의도치 않은 부작용을 일으킬 수 있습니다).

레지스트리 설계

매핑 인프라는 다음과 같은 구성 요소와 특성을 갖추도록 설계되었습니다:

  • PEP 725 식별자(DepURL)의 중앙 레지스트리입니다. 여기에는 적어도 표준으로 간주되는 잘 알려진 generic 및 virtual 식별자가 포함됩니다.
  • 알려진 생태계의 목록입니다. 생태계 유지 관리자는 여기에 자신의 이름 매핑을 등록할 수 있습니다.
  • 매핑의 구조를 정의하는 표준화된 스키마입니다. 각 매핑은 자신이 지원하는 패키지 관리자의 동작 방식에 대한 세부 정보를 프로그램이 처리할 수 있는 형태로 제공할 수도 있습니다.

위 문서들은 함께 제공되는 JSON 스키마로 검증되는 JSON 파일 형태로 제공됩니다. 이러한 리소스를 조회하고 활용할 수 있도록 Python 라이브러리와 CLI가 제공됩니다. 사용자는 기본 패키지 매핑과 명령 생성에 사용할 시스템 패키지 관리자를 설정할 수 있습니다(예: Ubuntu 사용자는 외부 의존성을 제공할 패키지 관리자로 apt 대신 conda, brew 또는 spack을 선호할 수 있습니다).

명세

중앙 레지스트리

중앙 레지스트리는 어떤 식별자가 정규 식별자로 인정되는지를 정의하며, 알려진 별칭도 함께 정의합니다.

중앙 레지스트리가 있으면 [external] 테이블을 검증할 수 있습니다. 관련된 모든 도구는 제공된 식별자가 올바른 형식인지 반드시 확인해야 합니다(MUST). 또한 일부 도구는 사용 중인 식별자가 정규 식별자로 인정되는지 확인할 수 있습니다(MAY). 구체적으로는 다음과 같습니다:

  • 빌드 백엔드, 빌드 프런트엔드, 설치 프로그램은 기본적으로 식별자가 정규 형식인지 검증해서는 안 됩니다(SHOULD NOT).
  • twine 같은 업로드 도구는 식별자가 정규 식별자인지 검증하여 사용자에게 경고하거나 오류를 보고해야 하며(SHOULD), 이를 끌 수 있는 방법도 제공해야 합니다. 가능한 경우 이를 대체할 정규 식별자를 제안해야 합니다(SHOULD).
  • PyPI 같은 색인 서버는 업로드 도구와 동일한 검증을 수행하고, 필요하면 아티팩트를 거부할 수 있습니다(MAY).

이 레지스트리는 또한 별칭 모음 중 어떤 항목을 정규 형식으로 선호할지, 또는 가상 DepURL에 어떤 버전 관리 체계를 적용할지(부록 B 참고)와 같이 그 내용에 관한 권위 있는 결정을 한곳에서 내려야 합니다(SHOULD). 이에 대한 답은 이 PEP에서 제시하지 않으며, 대신 그 책임을 중앙 레지스트리 유지 관리자에게 위임합니다.

중앙 레지스트리 문서의 정규 파일 이름은 반드시 registry.json이어야 합니다(MUST).

스키마

중앙 레지스트리는 다음 JSON 스키마로 명세됩니다:

$schema
Type string
Description URL of the definition list schema in use for the document.
Required False
schema_version
Type integer
Required False
definitions
Type array
Description List of DepURLs currently recognized.
Required True

이 목록의 각 항목은 다음과 같이 정의됩니다:

Field Type Description
id (required) string matching regex ^dep:.+$ The entry identifier MUST be a valid DepURL string.
description string Free-form field to add some details about the package. Allows Markdown.
provides DepURLField | list[DepURLField] List of id strings this entry connects to. Useful to annotate aliases (e.g. dep:generic/arrow and dep:github/apache/arrow) or virtual package implementations (e.g. dep:generic/gcc would provide dep:virtual/compiler/c). This field MUST NOT be present in dep:virtual/ definitions. Entries without provides content or, if populated, only with dep:virtual/ identifiers, are considered canonical.
urls AnyUrl | list[AnyUrl] | dict[NonEmptyString, AnyUrl] Hyperlinks to web locations that provide more information about the definition.

알려진 생태계

알려진 생태계 목록에는 두 가지 역할이 있습니다:

  1. 특정 생태계 매핑의 정규 URL을 알립니다.
  2. 매핑 절에서 설명한 대로 각 생태계에 고유하고 짧은 식별자를 부여합니다.

알려진 생태계 목록의 정규 파일 이름은 반드시 known-ecosystems.json이어야 합니다(MUST).

스키마

알려진 생태계 목록은 다음 JSON 스키마로 명세됩니다:

$schema
Type string
Description URL of the schema in use for the document.
Required False
schema_version
Type integer
Description Version of the schema in use.
Required False
ecosystems
Type dict
Description Ecosystems names and their corresponding details.
Required True

이 딕셔너리는 생태계 식별자를 가리키는 비어 있지 않은 문자열 키를 다음과 같이 정의된 하위 딕셔너리에 매핑합니다:

Key Value type Value description
mapping (required) AnyURL URL to the mapping for this ecosystem.

매핑

매핑은 중앙 레지스트리에서 사용할 수 있는 정식 항목을 어떤 생태계별 식별자가 제공하는지 지정합니다. 매핑은 주로 두 개의 딕셔너리 리스트로 구성됩니다. 하나는 각 항목이 DepURL을 하나 이상의 생태계별 식별자에 매핑하는 리스트이고, 다른 하나는 하나 이상의 패키지 관리자를 사용하는 방법을 알려 주는 리스트입니다.

각 매핑은 온라인으로 가져올 수 있는 정식 URL을 반드시 가져야 합니다. 전체 파일 이름은 반드시 {ecosystem-identifier}.mapping.json이어야 하며, 여기서 “생태계 식별자”는 반드시 다음 정규식을 따라야 합니다: [a-z0-9\-_.]+(\+[a-z0-9\-_.]+)?. 즉, 첫 번째 필드가 오고, 그 뒤에 + 기호로 구분된 두 번째 필드가 선택적으로 이어지는 형태입니다.

Linux 배포판에 해당하는 생태계의 경우, 첫 번째 필드는 os-release 명세에 정의된 ID 문자열과 일치해야 합니다(MUST). 두 번째 필드가 제공되고 해당 사항이 있는 경우, 이 필드는 반드시 VERSION_ID 문자열과 일치해야 합니다.

버전 필드는 선택 사항이므로, 도구는 버전이 포함된 식별자에 접근을 시도하되 찾지 못하면 이름만 있는 식별자로 대체해야 합니다.

스키마

매핑은 다음 JSON Schema로 명세됩니다:

$schema
Type string
Description URL of the mappings schema in use for the document.
Required False
schema_version
Type integer
Description Version of the schema in use.
Required False
name
Type string
Description Display name for the mapping.
Required True
description
Type string
Description Free-form field to add information this mapping. Allows Markdown.
Required False
mappings
Type array
Description List of DepURL-to-specs mappings.
Required True

mappings의 각 항목은 다음과 같이 정의됩니다:

Field Type Description
id (required) string matching regex ^dep:.+$ DepURL, as provided in the central registry.
description string Free-form field to add some details about the package. Allows Markdown.
specs † string | list[string] | dict[Literal['build', 'host', 'run'], string | list[string]] Ecosystem-specific identifiers for this package. The full form is a dictionary that maps the categories build, host and run to their corresponding package identifiers. As a shorthand, a single string or a list of strings can be provided, in which case will be used to populate the three categories identically. An empty list indicates that the ecosystem does not have packages for this entry.
specs_from † string matching regex ^dep:.+$ DepURL identifier of another entry whose specs will be reused here.
urls AnyUrl | list[AnyUrl] | dict[NonEmptyString, AnyUrl] Hyperlinks to web locations that provide more information about the definition.
extra_metadata dict[NonEmptyString, Any] Free-form key-value store for arbitrary metadata.

† specs와 specs_from 중 정확히 하나가 반드시 있어야 합니다.

package_managers
Type array
Description List of tools that can be used to install packages in this ecosystem.
Required True

package_managers의 각 항목은 반드시 다음 필드를 가진 딕셔너리여야 합니다:

Field Type Description
name (required) string Short identifier for this package manager (usually the command name).
commands (required) dict See subsection below.
specifier_syntax (required) dict See subsection below.
commands

주어진 패키지를 설치하거나 조회하는 데 사용하는 명령입니다.

이 값은 반드시 딕셔너리여야 하며, 허용되는 키는 반드시 install (설치 지침 생성용)과 query (주어진 패키지가 이미 설치되어 있는지 확인하는 용도) 두 개뿐이어야 합니다. 각 키의 값은 반드시 다음을 포함하는 딕셔너리여야 합니다:

  • 필수 command 키로, 반드시 (subprocess.run이 기대하는 형식의) 문자열 리스트를 값으로 받아야 합니다. 이 리스트에서 정확히 한 항목은 반드시 {} 자리 표시자여야 하며, 이 자리 표시자는 매핑된 패키지 지정자로 대체됩니다.
  • 명령을 상승된 권한(예: Windows에서는 관리자, Linux와 macOS에서는 슈퍼유저)으로 실행해야 하는지를 나타내는 선택적 requires_elevation 불리언입니다(기본값은 False).
  • 명령이 여러 패키지 지정자를 동시에 받는지를 결정하는 필수 multiple_specifiers 열거형으로, 다음 중 하나의 값을 가집니다:
    • always: install의 기본값입니다.
    • name-only: 지정자에 버전 제약이 없는 경우에만 명령이 여러 지정자를 받습니다.
    • never, query의 기본값입니다.

install 명령은 자리 표시자가 여러 지정자로 치환되는 것을 지원해야 합니다(SHOULD). query는 명령마다 단 하나의 지정자만 받아야 합니다(MUST).

install의 경우, 패키지가 성공적으로 설치되었거나 이미 설치되어 있으면 종료 코드는 0이어야 합니다(MUST).

query의 경우, 패키지가 설치되어 있으면 명령의 종료 코드는 0이어야 합니다(MUST). 그렇지 않으면 0이 아닌 종료 코드를 반환해야 합니다(MUST).

specifier_syntax

(PEP 725에서 정한) PEP 440 지정자의 부분집합을 대상 패키지 관리자에 매핑하는 방법에 대한 지침을 기술하는 딕셔너리입니다. 이름만 지원, 정확한 버전만 지원, 버전 범위 호환(연산자별 변환 포함)의 세 가지 지원 수준을 제공합니다. 따라서 이 세 개의 최상위 키는 필수여야 합니다(MUST). 추가 키는 허용되어서는 안 됩니다(MUST NOT).

  • name_only는 버전 정보를 포함하지 않는 지정자에 사용하는 문법으로서 문자열 리스트를 받아야 하며(MUST), 자리 표시자 {name}을 포함해야 합니다(MUST).
  • exact_version은 None이거나 정확한 버전 제약만을 표현하는 지정자에 사용하는 문법을 기술하는 문자열 리스트여야 합니다(MUST). 후자의 경우 자리 표시자 {name}과 {version}은 적어도 하나의 문자열에 존재해야 합니다(MUST)(두 자리 표시자가 반드시 같은 문자열에 있을 필요는 없습니다).
  • version_ranges는 None이거나 다음 필수 키를 가진 딕셔너리여야 합니다(MUST).
    • 키 syntax는 문자열 리스트를 받으며, 그중 적어도 하나는 {ranges} 자리 표시자를 포함해야 합니다(MUST)(이 자리 표시자는 and의 값에 따라 결합되었을 수도 있는 버전 제약으로 치환됩니다). 이 문자열들은 {name} 자리 표시자를 포함할 수도 있습니다(MAY).
    • 키 equal, greater_than, greater_than_equal, less_than, less_than_equal은 해당 연산자가 지원되면 문자열을, 그렇지 않으면 None을 받습니다. 전자의 경우, 값은 {version} 자리 표시자를 포함해야 하며(MUST), {name}을 포함할 수도 있습니다(MAY).
    • 키 and는 여러 버전 제약을 하나의 토큰으로 결합하는 데 사용하는 문자열을 받으며, 토큰마다 제약을 하나만 사용할 수 있다면 None을 받습니다. 후자의 경우, 서로 다른 제약들은 syntax 템플릿을 사용하여 여러 토큰으로 “분해”됩니다.

    exact_version 또는 version_ranges가 None으로 설정되면, 패키지 관리자가 해당 종류의 지정자를 지원하지 않는다는 뜻입니다.

Note

specifier_syntax 매핑은 설치할 패키지 버전을 선택할 수 있는 생태계 간의 상호 운용성을 제공하기 위한 것입니다. 예를 들어, 많은 Linux 배포판에서는 그렇지 않습니다. 이러한 배포판에서는 각 배포판 릴리스가 수명 주기 동안 하나의 패키지 버전으로 고정하기로 커밋합니다(다만 필요한 보안 백포트는 적용되는 경우가 많습니다).

이런 경우에는 OS가 제공하는 버전이 적합하기를 기대하며 install 명령을 낙관적으로 “이름만” 모드로 사용할 수 있습니다. 좀 더 비관적인 대안은 먼저 query 명령으로 사용 가능한 버전이 프로젝트 제약과 맞는지 확인한 다음, 이름으로 패키지를 설치하는 것입니다.

그런 경우에도 생태계마다 업스트림 릴리스를 재패키징된 버전에 대응시키는 방식이 다르기 때문에 완벽한 1:1 버전 대응이 항상 가능한 것은 아닙니다(예: 릴리스 스키마 변경을 수용하기 위해 epoch를 올려야 했던 경우). 이러한 점에서, epoch나 사전 릴리스에 대해서는 명시적인 매핑 의미론을 정의하지 않습니다.

재배포

중앙 레지스트리, 알려진 생태계 목록, 매핑 문서는 각 플랫폼에서 오프라인 배포용으로 패키징될 수 있습니다(MAY).

저자들은 이를 각 운영 체제의 데이터 산출물 표준 위치에 두기를 권장합니다. 예를 들어 Linux 등에서는 $XDG_DATA_DIRS, macOS에서는 ~/Library/Application Support, Windows에서는 %LOCALAPPDATA%입니다. 하위 디렉터리 식별자는 external-packaging-metadata-mappings여야 하며(MUST), 앞서 언급한 스키마에 해당하는 문서만 포함해야 합니다(SHOULD). 이 문서들은 정식 파일 이름을 사용해야 합니다(MUST).

예시

레지스트리

단순화한 레지스트리는 다음과 같은 모습입니다:

{
  "$schema": "https://raw.githubusercontent.com/jaimergp/external-metadata-mappings/main/schemas/central-registry.schema.json",
  "schema_version": 1,
  "definitions": [
    {
      "id": "dep:generic/zlib",
      "description": "A Massively Spiffy Yet Delicately Unobtrusive Compression Library"
    },
    {
      "id": "dep:generic/libwebp",
      "description": "WebP codec is a library to encode and decode images in WebP format. This package contains the library that can be used in other programs to add WebP support"
    },
    {
      "id": "dep:generic/clang",
      "description": "Language front-end and tooling infrastructure for languages in the C language family for the LLVM project."
    }
  ]
}

알려진 생태계

항목이 하나뿐인 최소한의 알려진 생태계 목록은 다음과 같은 모습입니다:

{
  "$schema": "https://raw.githubusercontent.com/jaimergp/external-metadata-mappings/main/schemas/known-ecosystems.schema.json",
  "schema_version": 1,
  "ecosystems": {
    "conda-forge": {
      "mapping": "https://raw.githubusercontent.com/jaimergp/external-metadata-mappings/refs/heads/main/data/conda-forge.mapping.json"
    }
}

대표적인 식별자 몇 가지는 다음과 같습니다:

Ecosystem Identifier Filename
Debian Bookworm debian+12 debian+12.mapping.json
Fedora 40 fedora+40 fedora+40.mapping.json
Ubuntu 24.04 ubuntu+24.04 ubuntu+24.04.mapping.json
Arch Linux (rolling) arch arch.mapping.json
Homebrew homebrew homebrew.mapping.json
conda-forge conda-forge conda-forge.mapping.json

매핑

간결함을 위해 항목을 몇 개만 넣은 가상의 conda-forge 매핑(conda-forge.mapping.json)은 다음과 같은 모습일 수 있습니다:

{
  "schema_version": 1,
  "name": "conda-forge",
  "description": "Mapping for the conda-forge ecosystem",
  "mappings": [
    {
      "id": "dep:generic/zlib",
      "description": "Massively spiffy yet delicately unobtrusive compression library.",
      "specs": "zlib",  // Simplest form
      "urls": {
        "feedstock": "https://github.com/conda-forge/zlib-feedstock"
      }
    },
    {
      "id": "dep:generic/libwebp",
      "description": "WebP image library. libwebp-base ships libraries; libwebp ships the binaries.",
      "specs": {  // expanded form with single spec per category
        "build": "libwebp",
        "host": "libwebp-base",
        "run": "libwebp"
      },
      "urls": {
        "feedstock": "https://github.com/conda-forge/libwebp-feedstock"
      }
    },
    {
      "id": "dep:generic/clang",
      "description": "Development headers and libraries for Clang",
      "specs": { // expanded form with specs list
        "build": [
          "clang",
          "clangxx"
        ],
        "host": [
          "clangdev"
        ],
        "run": [
          "clang",
          "clangxx",
          "clang-format",
          "clang-tools"
        ]
      },
      "urls": {
        "feedstock": "https://github.com/conda-forge/clangdev-feedstock"
      }
    },
  ],
  "package_managers": [
    {
      "name": "conda",
      "commands": {
        "install": {
          "command": [
            "conda",
            "install",
            "{}"
          ],
          "multiple_specifiers": "always",
          "requires_elevation": false,
        },
        "query": {
          "command": [
            "conda",
            "list",
            "-f",
            "{}"
          ],
          "multiple_specifiers": "never",
          "requires_elevation": false,
        }
      },
      "specifier_syntax": {
        "exact_version": [
          "{name}=={version}"
        ],
        "name_only": [
          "{name}"
        ],
        "version_ranges": {
          "and": ",",
          "equal": "={version}",
          "greater_than": ">{version}",
          "greater_than_equal": ">={version}",
          "less_than": "<{version}",
          "less_than_equal": "<={version}",
          "syntax": [
            "{name}{ranges}"
          ]
        }
      }
    }
  ]
}

실제 예시

다음 저장소는 실제 사례에서 이러한 스키마가 어떤 모습일 수 있는지 보여 주는 예시를 제공합니다. 이 예시는 규범을 정하려는 것이 아니라, 이러한 스키마를 적용하는 방법을 보여 주기 위한 것일 뿐입니다:

pyproject-external CLI

다음 예시는 이름 매핑 메커니즘을 어떻게 사용할 수 있는지 보여 줍니다. 이 예시에서는 pyproject-external 패키지의 일부로 구현된 CLI를 사용합니다.

my-cxx-pkg라는 Python 패키지의 소스를 클론했다고 가정합시다. 이 패키지에는 C++로 구현된 확장 모듈이 하나 있으며, 이 모듈은 zlib에 링크되고 pybind11을 사용합니다. 빌드 백엔드로는 meson-python을 사용합니다:

[build-system]
build-backend = 'mesonpy'
requires = [
  "meson-python>=0.13.1",
  "pybind11>=2.10.4",
]

[external]
build-requires = [
  "dep:virtual/compiler/cxx",
]
host-requires = [
  "dep:generic/zlib",
]

Ubuntu의 apt에 대한 이름 매핑이 모두 갖춰져 있다면 다음과 같이 표시될 수 있습니다:

# show all external dependencies as DepURLs
$ python -m pyproject_external show .
[external]
build-requires = [
    "dep:virtual/compiler/cxx",
]
host-requires = [
    "dep:generic/zlib",
]

# show all external dependencies, but mapped to the autodetected ecosystem
$ python -m pyproject_external show --output=mapped .
[external]
build-requires = [
    "g++",
    "python3",
]
host-requires = [
    "zlib1g",
    "zlib1g-dev",
]

# show how to install external dependencies
$ python -m pyproject_external show --output=command .
sudo apt install --yes g++ zlib1g zlib1g-dev python3

아직 해당 설치 명령을 실행하지 않았으므로 외부 의존성이 누락되어 있을 수 있습니다. 빌드가 실패하면 출력은 다음과 같을 수 있습니다:

$ pip install .
...
× Encountered error while generating package metadata.
╰─> See above for output.

note: This is an issue with the package mentioned above, not pip.

This package has the following external dependencies, if those are missing
on your system they are likely to be the cause of this build failure:

  dep:virtual/compiler/cxx
  dep:generic/zlib

Pip이 이름 매핑 레지스트리 조회 기능을 지원하게 된다면, 해당 메시지의 끝부분은 다음과 같이 개선될 수 있습니다:

The following external dependencies are needed to install the package
mentioned above. You may need to install them with `apt`:

  g++
  zlib1g
  zlib1g-dev

사용자가 외부 의존성을 설치할 때 conda 패키지와 mamba 패키지 관리자를 사용하려는 경우, ~/.config/pyproject-external/config.toml (또는 이에 상응하는) 파일에 이를 지정할 수 있습니다:

preferred_package_manager = "mamba"

그러면 pyproject-external의 출력이 다음과 같이 바뀝니다:

$ python -m pyproject_external show --output command .
mamba install --yes --channel=conda-forge --channel-priority=strict cxx-compiler zlib python

pyproject-external CLI는 중앙 레지스트리를 기준으로 [external] 테이블을 검증하여 식별자가 정식(canonical)으로 간주되는지 확인하는 간단한 방법도 제공합니다:

$ python -m pyproject_external show --validate grpcio-1.71.0.tar.gz
WARNING  Dep URL 'dep:virtual/compiler/cpp' is not recognized in the
central registry. Did you mean any of ['dep:virtual/compiler/c',
'dep:virtual/compiler/cxx', 'dep:virtual/compiler/cuda',
'dep:virtual/compiler/go', 'dep:virtual/compiler/c-sharp']?
[external]
build-requires = [
    "dep:virtual/compiler/c",
    "dep:virtual/compiler/cpp",
]

pyproject-external API

pyproject-external Python API를 사용하면 이러한 작업을 프로그래밍 방식으로도 수행할 수 있습니다:

>>> from pyproject_external import External
>>> external = External.from_pyproject_data(
      {
        "external": {
          "build-requires": [
            "dep:virtual/compiler/c",
            "dep:virtual/compiler/cpp",
          ]
        }
      }
    )
>>> external.validate()
Dep URL 'dep:virtual/compiler/cpp' is not recognized in the central registry. Did you
mean any of ['dep:virtual/compiler/c', 'dep:virtual/compiler/cxx',
'dep:virtual/compiler/cuda', 'dep:virtual/compiler/go', 'dep:virtual/compiler/c-sharp']?
>>> external = External.from_pyproject_data(
      {
        "external": {
          "build-requires": [
            "dep:virtual/compiler/c",
            "dep:virtual/compiler/cxx",  # fixed
          ]
        }
      }
    )
>>> external.validate()
>>> external.to_dict()
{'external': {'build_requires': ['dep:virtual/compiler/c', 'dep:virtual/compiler/cxx']}}
>>> from pyproject_external import detect_ecosystem_and_package_manager
>>> ecosystem, package_manager = detect_ecosystem_and_package_manager()
>>> ecosystem
'conda-forge'
>>> package_manager
'pixi'
>>> external.to_dict(mapped_for=ecosystem, package_manager=package_manager)
{'external': {'build_requires': ['c-compiler', 'cxx-compiler', 'python']}}
>>> external.install_commands(ecosystem, package_manager=package_manager)
# {"command": ["pixi", "add", "{}"]}
[
  ['pixi', 'add', 'c-compiler', 'cxx-compiler', 'python'],
]
>>> external.query_commands(ecosystem, package_manager=package_manager)
# {"command": ["pixi", "list", "{}"]}
[
  ['pixi', 'list', 'c-compiler'],
  ['pixi', 'list', 'cxx-compiler'],
  ['pixi', 'list', 'python'],
]

Grayskull

Python 패키지용 conda 레시피 제너레이터인 Grayskull에 conda/grayskull#518을 통해 개념 증명용 프로토타입 구현이 기여되었습니다.

이제 우리 패키지의 레시피 제너레이터에서 이름 매핑을 사용하기 위해 Grayskull을 실행할 수 있습니다:

$ grayskull pypi my-cxx-pkg
#### Initializing recipe for my-cxx-pkg (pypi) ####

Recovering metadata from pypi...
Starting the download of the sdist package my-cxx-pkg
my-cxx-pkg 100% Time:  0:00:10   5.3 MiB/s|###########|
Checking for pyproject.toml
...

Build requirements:
  - python                                 # [build_platform != target_platform]
  - cross-python_{{ target_platform }}     # [build_platform != target_platform]
  - meson-python >= 0.13.1                 # [build_platform != target_platform]
  - pybind11 >= 2.10.4                     # [build_platform != target_platform]
  - ninja                                  # [build_platform != target_platform]
  - libboost-devel                         # [build_platform != target_platform]
  - {{ compiler('cxx') }}
Host requirements:
  - python
  - meson-python >=0.13.1
  - pybind11 >=2.10.4
  - ninja
  - libboost-devel
Run requirements:
  - python

#### Recipe generated on /path/to/recipe/dir for my-cxx-pkg ####

하위 호환성

하위 호환성에 미치는 영향은 없습니다.

보안 영향

이 제안은 기존 프로젝트에 어떠한 보안상의 영향도 주지 않습니다. 제안된 스키마, 레지스트리 및 매핑은 다운스트림 도구가 각자 적합하다고 판단하는 방식으로 자유롭게 사용할 수 있는 자원입니다.

다만 향후 구현자를 위한 몇 가지 권장 사항이 있습니다. 매핑 스키마는 명령 실행 지시를 담기 위한 필드(package_managers[].commands)를 제안합니다. 변조된 매핑은 이러한 지시를 다른 내용으로 바꿀 수 있습니다. 따라서 도구는 온라인 출처에서 매핑을 가져오기 위해 인터넷 연결에 의존해서는 안 됩니다. 대신 다음 중 하나를 따라야 합니다:

  • 배포되는 패키지에 관련 문서를 함께 포함(vendoring)하거나,
  • 미리 패키징된 이러한 문서의 오프라인 배포본에 의존하거나,
  • 가져온 문서의 진위 검증을 위한 모범 사례를 구현해야 합니다.

설치 명령은 사용자의 시스템 구성을 변경할 가능성이 있습니다. 가능한 경우, 도구는 외부 의존성 설치를 위해 일시적이고 격리된 환경을 생성하는 방식을 우선해야 합니다. 해당 생태계가 이러한 기능을 기본적으로 제공하지 않는다면, 컨테이너화와 같은 다른 해결책을 사용할 수 있습니다. 최소한 작업이 미치는 영향을 알려 주는 안내 메시지는 제공해야 합니다.

교육 방법

이 PEP의 내용을 숙지해야 할 수 있는 대상은 적어도 네 부류가 있습니다:

  1. 잘 알려진 DepURL과 매핑된 생태계 목록을 관리하는 책임을 지는 중앙 레지스트리 유지 관리자.
  2. 자신의 생태계에 대한 매핑을 최신 상태로 유지하는 책임을 지는 패키징 생태계 유지 관리자.
  3. 외부 의존성을 필요로 하는 Python 프로젝트의 유지 관리자.
  4. 외부 의존성 메타데이터가 있는 패키지의 최종 사용자.

중앙 DepURL 레지스트리 유지 관리자

중앙 DepURL 레지스트리 유지 관리자는 DepURL 모음과 알려진 생태계를 관리합니다. 이 기여자들은 새 DepURL을 언제 정의할 수 있는지에 대해 명확하게 정의된 규칙을 참조할 수 있어야 합니다. 정식 DepURL 정의를 느슨하게 다루는 것은 바람직하지 않습니다. 정의가 하나 추가될 때마다 대상 생태계의 매핑을 유지 관리하는 부담이 늘어나기 때문입니다.

중앙 레지스트리 유지 관리자는 기본 규칙에 합의하고 이를 저장소 문서의 일부로 작성해야 하며, 이슈 및 풀 리퀘스트 템플릿이나 린트 도구와 같은 추가적인 보조 수단으로 이를 뒷받침할 수도 있습니다.

패키지 생태계 유지 관리자의 사용 방법

매핑 항목이 누락되면 영향을 받는 생태계의 최종 사용자에게 맞춤형 오류 메시지와 기타 UX 보조 수단이 제공되지 않습니다. 따라서 각 패키지 생태계는 자신의 매핑을 중앙 레지스트리에 맞춰 최신 상태로 유지할 것을 권장합니다. 이를 위한 핵심은 린트 스크립트(external-metadata-mappings의 예시를 참고하십시오)나 이슈 또는 초안 제출을 통한 주기적인 알림 같은 자동화가 될 것입니다.

초기 매핑을 구축하는 데에는 많은 작업이 필요할 가능성이 높지만, 이상적으로는 이후 지속적인 유지 관리에 드는 노력은 더 적어야 합니다.

모범 사례가 발견되고 합의되면, 이를 매핑 유지 관리자를 위한 학습 자료로서 중앙 레지스트리 저장소에 문서화해야 합니다.

Python 프로젝트의 유지 관리자

패키지 유지 관리자의 책임은 자신의 패키지가 필요로 하는 외부 의존성을 가장 잘 나타내는 DepURL을 결정하는 것입니다. 이는 PEP 725에서 다루며, external-metadata-mappings.streamlit.app에 있는 대화형 매핑 브라우저 데모가 도움이 될 수 있습니다. 중앙 레지스트리 문서에는 새로 참여하는 사람들의 결정을 돕기 위한 예시와 자주 묻는 질문이 포함될 수 있습니다.

특정 의존성에 적합한 DepURL이 없다면, 유지 관리자는 중앙 레지스트리에 요청을 제출하는 것을 고려할 수 있습니다. 요청 방법에 대한 안내는 중앙 레지스트리 문서의 일부로 제공되어야 합니다.

최종 사용자 패키지 소비자

기본적으로 사용자 경험에는 아무런 변화가 없습니다. 사용자가 휠에만 의존하는 경우에는 특히 그렇습니다. 유일한 영향은 외부 런타임 의존성(드물 것으로 예상됩니다)에서 비롯되며, 그런 경우에도 호환되는 도구를 설치하여 옵트인해야 하기 때문입니다.

옵트인한 사용자는 대상 생태계에 대한 항목이 누락된 것을 발견할 수 있으며, 이 경우 관련 문서 섹션을 안내하는 유익한 오류 메시지를 받아야 합니다. 이를 통해 사용자는 문제의 성격과 가능한 해결책을 파악할 수 있습니다.

이로써 그중 일부 사용자가 누락된 항목을 보고하거나, 영향을 받는 매핑에 수정 사항을 제출하거나, 매핑이 아예 없는 경우에는 직접 새 매핑을 유지 관리하기로 결정하기를 바랍니다. 이를 위해 이들은 (위에서 논의한) 매핑 유지 관리자의 책임을 숙지해야 합니다.

참조 구현

참조 구현에는 다음 세 가지 구성 요소가 포함되어야 합니다:

  1. 최소한 DepURL과 그 설명을 담는 중앙 레지스트리. 이 레지스트리는 패키지 생태계 매핑의 세부 사항을 포함해서는 안 됩니다(MUST NOT).
  2. 매핑 모음에 대한 표준 명세. JSON Schema는 많은 텍스트 편집기에서 스키마로 널리 사용되므로, 표준 명세를 표현하는 데 자연스러운 선택이 될 것입니다.
  3. (2)의 구현으로, 중앙 레지스트리의 내용을 생태계별 패키지 이름으로 매핑하는 기능을 제공합니다.

(1)의 경우, JSON 스키마는 central-registry.schema.json에 정의되어 있습니다. 예시 레지스트리는 registry.json에서 확인할 수 있습니다. (2)의 경우, JSON 스키마는 external-mapping.schema.json에 정의되어 있습니다. 일부 패키지를 대상으로 한 예시 매핑 모음은 external-metadata-mappings에서 확인할 수 있습니다. (3)의 경우, JSON 스키마는 known-ecosystems.schema.json에 정의되어 있습니다. 예시 목록은 known-ecosystems.json에서 확인할 수 있습니다. JSON 스키마는 이 Pydantic 모델들로 생성됩니다.

여러 JSON 문서와 [external] 테이블을 사용하기 위한 참조 CLI 및 Python API는 pyproject-external에서 확인할 수 있습니다.

기각된 아이디어

동일한 기구가 관리하는 중앙 집중식 매핑

레지스트리를 관리하는 중앙 기구는 유용하지만, 여러 생태계의 매핑을 처리하는 유지 관리 부담은 PyPI 규모에서는 감당할 수 없습니다. 따라서 중앙 기구는 중앙 레지스트리와 알려진 생태계 목록만 관리하고, 매핑 자체의 유지 관리는 대상 생태계가 맡을 것을 제안합니다.

생태계별 패키지 변형 허용

일부 생태계에는 잘 알려진 패키지의 자체 변형이 있습니다. 예를 들어 Debian의 libsymspg2-dev가 그렇습니다. dep:deb/debian/libsymspg2-dev 같은 식별자는 구문상 유효하지만, 중앙 레지스트리는 이를 잘 알려진 식별자로 인정하지 않고 대신 그에 대응하는 generic 식별자를 우선해야 합니다. 사용자는 여전히 이를 사용할 수 있지만, 도구는 이에 대해 경고하고 일반(generic) 식별자를 사용하도록 제안할 수 있습니다. 이는 여러 플랫폼과 운영 체제에서의 도입을 쉽게 하기 위해, 가능한 한 생태계에 구애받지 않는 메타데이터 사용을 장려하려는 것입니다.

중앙 레지스트리에 더 많은 패키지 메타데이터 추가

중앙 레지스트리에는 DepURL 목록과 그 식별을 돕기 위한 최소한의 메타데이터 필드(자유 형식 텍스트 설명과 관련 위치를 가리키는 하나 이상의 URL)만 포함되어야 합니다.

저희는 추가 세부 정보를 중앙 레지스트리에서 제외하기로 했으며, 그 대신 외부 기여자가 자체 매핑을 유지 관리하면서 자유 형식의 extra_metadata 필드를 통해 식별자에 추가 메타데이터를 주석으로 달 것을 권장합니다.

그 이유는 다음과 같습니다.

  • 기존 필드만으로도 추가 메타데이터를 얻을 수 있는 프로젝트 홈을 식별하기에 충분합니다(예: 해당 URL의 저장소에는 저작자와 라이선스에 관한 세부 정보가 포함되어 있을 가능성이 높습니다).
  • 이러한 세부 정보는 실제 대상 생태계에서도 얻을 수 있습니다. 경우에 따라서는 이 편이 오히려 더 나을 수도 있습니다. 예를 들어 라이선스의 경우, 다운스트림 패키징 과정에서 번들된 의존성을 분리하거나 선택적 요소를 조정하면서 실제로 라이선스가 달라질 수 있습니다.
  • 이러한 세부 사항은 프로젝트 수명 동안 바뀔 수 있으며, 이를 최신 상태로 유지하는 것은 거버넌스 기구의 유지 관리 부담을 늘릴 것입니다.
  • 따라서 추가 메타데이터를 중앙에서 관리하면, 서로 다른 버전이 제공되거나 요구될 수 있는 대상 생태계들 사이에 모호함과 불일치가 생깁니다.

PyPI 프로젝트를 대상 생태계에서 재패키징된 대응 패키지에 매핑

다른 생태계가 자체 패키징 시스템으로 Python 프로젝트를 재배포하는 일은 흔합니다. 이는 컴파일된 확장이 있는 패키지에는 필요하지만, 순수 Python 휠에는 이론적으로 불필요합니다. 이 작업이 필요한 유일한 이유는 메타데이터 변환인 것으로 보입니다. 이러한 방향의 논의 예시는 Wanting a singular packaging tool/vision #68, Wanting a singular packaging tool/vision #103, spack/spack#28282를 참고하십시오.

이 PEP의 제안은 PyPI -> 생태계 매핑을 고려하지 않지만, 같은 스키마를 그 용도로 재활용할 수 있습니다. 어쨌든 PyPI 이름으로부터 PURL이나 DepURL을 만드는 것은 간단합니다(예: numpy는 pkg:pypi/numpy가 됩니다). 가상의 매핑 유지 관리자는 자신의 재패키징 작업에 원본 PURL 식별자를 주석으로 달고, 그 메타데이터를 사용해 다음과 같은 호환 매핑을 생성할 수 있습니다.

{
  "$schema": "https://raw.githubusercontent.com/jaimergp/external-metadata-mappings/main/schemas/external-mapping.schema.json",
  "schema_version": 1,
  "name": "PyPI packages in Ubuntu 24.04",
  "description": "PyPI mapping for the Ubuntu 24.04 LTS (Noble) distro",
  "mappings": [
    {
      "id": "dep:pypi/numpy",
      "description": "The fundamental package for scientific computing with Python",
      "specs": ["python3-numpy"],
      "urls": {
        "home": "https://numpy.org/"
      }
    }
  ]
}

이러한 매핑이 있으면 다운스트림 재배포 작업은 컴파일된 패키지에 집중하고, 순수 휠은 Python 패키징 솔루션에 직접 맡길 수 있습니다.

식별자의 엄격한 검증

중앙 레지스트리는 정식 식별자 목록을 제공하므로, 구현자는 제공된 모든 식별자가 실제로 정식 식별자인지 확인하고 싶어질 수 있습니다. 저희는 이 관행을 일부 도구 범주에 대해서만 권장하고, 어떤 경우에도 그러한 검사를 요구하지 않기로 결정했습니다.

[external] 메타데이터 테이블이 패키징 커뮤니티에 채택됨에 따라, 여러 프로젝트에서 나타나는 요구 사항을 수용하기 위해 정식 식별자 목록이 늘어날 것으로 예상됩니다. 예를 들어, 새로운 C++ 라이브러리나 새로운 언어 컴파일러가 도입되는 경우입니다.

검증이 너무 엄격해서 알 수 없는 식별자를 거부한다면, 외부 메타데이터 채택에 불필요한 마찰이 생기고, 새로 요청된 식별자를 사람이 시간에 쫓기며 검토하고 승인해야 하므로, 중앙 레지스트리에 새 식별자가 추가되어야 하는 패키지의 게시가 막힐 수도 있습니다.

저희는 제공된 식별자의 형식이 올바른지만 간단히 확인할 것을 제안합니다. 중앙 레지스트리가 널리 채택되어 성숙해지면, 향후 작업에서 식별자가 정식 식별자로 인정되는지도 강제하도록 할 수 있습니다.

상속과 상호 참조 매핑

매핑의 재사용성을 높일 수 있는 개선안으로, 부모 매핑을 상속하여 추가 값으로 확장하거나 대체하는 메커니즘을 제공하는 방안이 있습니다. 저자들은 이에 따르는 복잡성(예: URL 해석, 중첩된 의존성, 깨진 리소스의 가능성)을 고려하여 이 기능을 추가하지 않기로 결정했습니다. 대신 다음과 같은 대안을 제안합니다:

  • 매핑 작성자는 스크립트와 cron 작업을 통해 파생 매핑 생성을 자동화하십시오. 예를 들어, 부모 매핑을 가져와 필요한 수정 사항을 적용하고 대상 위치에 다시 게시하는 정도의 간단한 로직이라면 유지 관리 부담이 크지 않을 것입니다.
  • 사용자 지정 재정의로 특정 매핑을 확장하려는 최종 사용자를 위해, 클라이언트 측 도구는 이를 쉽게 할 수 있도록 필요한 기능을 구현해야 합니다. 예를 들어, pyproject-external 같은 도구는 다음과 같은 CLI 플래그나 환경 변수를 제공할 수 있습니다:
    • --use-mapping / <TOOL>_USE_MAPPING: 정식 위치 대신 주어진 로컬 또는 원격 매핑을 사용합니다.
    • --patch-mapping / <TOOL>_PATCH_MAPPING: 로컬 또는 원격 매핑이 주어지면, 정식 위치의 매핑에서 일치하는 키는 대체하고 일치하지 않는 키는 덧붙입니다.
    • --extend-mapping / <TOOL>_EXTEND_MAPPING: 로컬 또는 원격 매핑이 주어지면, 그 내용을 정식 매핑에 덧붙입니다. 매핑 옵션이 둘 이상 있을 때 도구가 사용자에게 그중 하나를 고를 수 있게 한다고 가정하면, 이 옵션은 완전히 재정의하지 않고도 선택지를 늘려 줍니다.

예를 들어, 다음과 같은 external 테이블이 있는 패키지가 있다고 가정해 봅시다:

[external]
build-requires = [
  "dep:virtual/compiler/c",
]
host-requires = [
  "dep:generic/libffi",
]

그리고 대상 생태계는 dep:virtual/compiler/c를 gcc에 매핑하지만 clang이 선호된다면, 다음과 같은 매핑 재정의를 제공할 수 있습니다:

{
  "$schema": "https://raw.githubusercontent.com/jaimergp/external-metadata-mappings/main/schemas/external-mapping.schema.json",
  "schema_version": 1,
  "name": "ecosystem override",
  "description": "Mapping override for my ecosystem of choice",
  "mappings": [
    {
      "id": "dep:virtual/compiler/c",
      "description": "Clang override",
      "specs": "clang"
    }
  ]
}

그러면 다음과 같이 사용합니다:

$ python -m my-tool show \
    sdist/cryptography-46.0.2.tar.gz \
    --output install-command \
    --patch-mapping=my-override.mapping.json

패키지 이름 변경 추적

패키징 생태계는 사용하는 명명 체계를 수정하고, 확장하고, 발전시키는 경향이 있습니다. 처음에는 하나의 모놀리식 빌드였던 것을 더 작은 구성 요소로 나누는 일은 흔합니다(예: 런타임 전용 환경에 개발 파일을 배포하지 않아 대역폭을 절약하기 위해). 일부 실무자는 SONAME이 바뀌는 동안 ABI 호환성을 추적하는 데 패키지 이름을 사용하기도 합니다. 이유는 여러 가지이고 다양할 수 있지만 문제는 같습니다. 하나의 업스트림 프로젝트 이름이 시간이 지나면서 서로 다른 이름으로 배포될 수 있다는 것입니다.

매핑에서 이러한 변경 사항을 추적하자는 제안에서는 추가 날짜 필드(valid_from, valid_to 등)를 포함하자고 했지만, 저자들은 이 아이디어를 기각하기로 결정했습니다. 이 방식은 구현을 복잡하게 만들고, 최신 상태로 유지하기 어려우며, 단순히 이력 기록 역할만 할 뿐 최종 사용자에게 가치를 더하지 않습니다.

그 대신, 버전이 있는 배포판은 릴리스마다 별도의 매핑을 유지할 것으로 기대합니다(제안된 매핑 명명 체계를 참고하십시오). 롤링 생태계는 필요하고 가능하다면 폐지 경고와 함께 별칭 패키지를 유지하도록 노력해야 합니다. 일반적으로, 최종 사용자가 필요할 때 이전 버전을 참고할 수 있도록 매핑 파일을 공개 버전 관리 시스템에서 관리할 것도 권장합니다.

기존 데이터베이스를 중앙 레지스트리로 재사용하기

여러 생태계를 아우르는 패키지 데이터베이스를 온라인에서 대략 검색해 보면 이 제안의 요구에 가깝지만 완전히 부합하지는 않는 여러 결과를 찾을 수 있습니다. 예를 들면 다음과 같습니다:

  • 일부 솔루션은 Repology나 pkgs.org 같이 Linux 배포판이나 Unix 시스템에만 초점을 맞춥니다.
  • Libraries.io와 같은 다른 서비스는 로그인이 필요합니다.
  • ecosyste.ms와 같은 다른 제공자는 API를 통해서만 이용할 수 있습니다.
  • purldb 서비스는 입력 요구 사항을 식별하는 추상 PURL이 아니라, 구체적인 PURL(특정 패키지 아티팩트를 식별함)을 수집하는 데에만 초점을 맞춥니다.

제안된 매핑은 라이브 서버와 API의 유지 관리를 요구하지 않으면서 최대한 가벼워지는 것을 지향합니다. 온라인과 오프라인에서 쉽게 갱신하고 배포할 수 있는 정적 JSON 파일의 모음일 뿐입니다.

앞으로 다음 기능을 제공하는 서비스가 생긴다면, 그 서비스는 이 PEP를 대체할 유력한 후보가 될 것입니다:

  • 소스 DepURL, PURL과 이들을 재패키징한 대응 항목 사이의 매핑을 제공합니다. 이는 PURL이 가상 패키지 개념과 사용하기 편한 버전 범위 표현식을 갖추게 되었음을 의미합니다.
  • 주어진 입력 PURL에 대해 패키지 관리자 명령을 생성할 수 있습니다.
  • 오프라인에서 사용할 수 있도록 로컬 아티팩트로 배포할 수 있습니다.
  • 라이브 서버나 API를 필요로 하지 않습니다.
  • FOSS 라이선스를 따릅니다.

미해결 문제

현재로서는 없습니다.

참고 문헌

부록 A: 운영 제안

생태계 매핑과 달리, 중앙 레지스트리와 알려진 생태계 목록은 중앙 기관이 유지 관리해야 합니다. 저자들은 다음을 제안합니다:

  • external-metadata-mappings 및 pyproject-external 저장소를 PyPA GitHub 조직(또는 PEP 772에 따른 동등한 조직) 아래에서 호스팅합니다.
  • 이 두 저장소를 위한 유지 관리자 팀을 만들되, 이 PEP의 작성자로 초기 구성원을 채우고 PEP 772에 따라 운영합니다.

부록 B: 가상 버전 관리 제안

가상 의존성은 비가상 의존성과 같은 문법으로 버전을 지정할 수 있지만, 그 의미가 모호할 수 있습니다(예를 들어 구현이 여러 개 있을 수 있고, 가상 인터페이스의 버전이 명확하게 매겨지지 않을 수 있습니다). 아래에서는 중앙 레지스트리 유지 관리자가 이러한 의미를 표준화할 때 고려할 수 있는 몇 가지 제안을 제시합니다.

  • OpenMP: 표준에 일반적인 MAJOR.MINOR 버전이 있으므로 >=4.5와 같은 형태가 됩니다.
  • BLAS/LAPACK: 표준 API가 무엇인지를 정의하는 Reference LAPACK의 버전 관리 방식을 사용해야 합니다. MAJOR.MINOR.MICRO를 사용하므로 >=3.10.0과 같은 형태가 됩니다.
  • 컴파일러: 컴파일러는 언어 표준을 구현합니다. C, C++, Fortran의 경우 이러한 표준은 연도로 버전이 매겨집니다. 버전이 올바르게 정렬되도록 전체 연도(네 자리)를 사용할 것을 권장합니다. 따라서 “최소 C99”는 >=1999가 되고, C++14 또는 Fortran 77을 선택하는 것은 각각 ==2014 또는 ==1977이 됩니다. 다른 언어는 다른 버전 관리 체계를 사용할 수 있습니다. 이러한 체계는 pyproject.toml에서 사용되기 전에 어딘가에 기술되어야 합니다.