기여 지침

PyPy는 매우 큰 프로젝트이며, 파고들기 어렵다는 평판이 있습니다. 이런 평판 중 일부는 정당하지만, 일부는 순전히 우연히 생긴 것입니다. 기여하고자 하는 모든 분이 배워야 할 중요한 교훈이 세 가지 있습니다:

  • PyPy는 여러 계층으로 이루어져 있습니다. 서로 매우 잘 분리된 아키텍처 구성 요소가 많이 있습니다. 이에 대해서는 아래에서 더 자세히 다루겠지만, 이 구조 때문에 어떤 것이 예상과 다른 계층에 있는 경우가 많습니다. 예를 들어 JIT 구현을 찾고 있다면, Python 프로그래밍 언어 구현에서는 찾을 수 없습니다.
  • 위와 같은 이유로, 저희는 테스트 주도 개발(Test Driven Development)을 매우 중요하게 여깁니다. 이는 저희가 믿는 방식이기도 하지만, PyPy의 아키텍처가 TDD를 염두에 두었을 때는 매우 잘 작동하고 그렇지 않을 때는 그다지 잘 작동하지 않기 때문이기도 합니다. 개발은 흔히 서로 관련 없는 한쪽 구석에서 단위 테스트를 하나씩 통과시키며 나아가다가, 마지막에 거대한 스위치를 켜서 모든 것을 하나로 합치는 방식으로 이루어집니다. (대개는 별다른 조치 없이도 바로 작동합니다. 그렇지 않다면 단위 테스트를 충분히 작성하지 않았다는 뜻입니다.) 다시 한번 강조할 만합니다. PyPy의 접근 방식은 TDD를 한다면 훌륭하지만, 그렇지 않다면 그다지 훌륭하지 않습니다.
  • PyPy는 완전히 다른 도구 모음을 사용하며, 그 대부분은 PyPy 저장소에 포함되어 있습니다. Makefile도 autoconf도 없습니다. 자세한 내용은 아래에서 설명합니다.

가장 먼저 기억해야 할 점은 PyPy 프로젝트가 세상의 다른 프로젝트 대부분과 매우 다르다는 것입니다. 또한 전통적인 컴파일러 프로젝트와도 다르기 때문에, 컴파일러에 관한 학술 강의 내용이 들어맞지 않거나 오히려 잘못된 방향으로 이끄는 경우가 많습니다. 하지만 실제 환경에서 런타임을 설계하고 구축하는 일이 어떻게 이루어지는지 이해하고 싶다면 이 프로젝트는 훌륭한 선택입니다!

참여하기

PyPy는 비교적 표준적인 오픈 소스 개발 프로세스를 따르고 있습니다. 첫 단계로 pypy-dev mailing list와 IRC 채널에 참여하시기를 권장합니다. 자세한 내용은 contact 섹션에서 확인하실 수 있습니다. 그곳 사람들은 매우 친절하며, 여러분이 올바른 방향으로 나아가도록 안내해 줄 수 있습니다.

저희는 보통 커밋 권한을 꽤 폭넓게 부여합니다. 따라서 PyPy로 무언가를 하고 싶으시다면 https://foss.heptapod.net 에 로그인한 뒤 PyPy group page에서 “Request Access” 링크를 클릭하여 “개발자”가 될 수 있습니다. 또한 저희는 코딩 스프린트도 진행합니다. 스프린트는 별도로 공지하며, 보통 the blog에 공지합니다.

여느 오픈 소스 프로젝트와 마찬가지로 이슈는 issue tracker에 등록해야 하며, 이슈를 수정하는 pull requests도 환영합니다.

더 읽을거리: 연락처

첫 기여

PyPy에 기여할 때 하지 말아야 할 일에 관한 첫 번째이자 가장 중요한 규칙은 “그냥 기능을 해킹해 넣는 것”입니다. 이렇게 해서는 잘 되지 않으며, 대개 PR을 상당 부분 다시 작업해야 한다는 것을 알게 될 것입니다. 그렇게 하면 안 되는 이유는 몇 가지가 있습니다:

  • 빌드에 시간이 오래 걸립니다
  • PyPy는 계층이 매우 철저하게 분리되어 있습니다
  • CPython 런타임의 컨텍스트가 필요한 경우가 많습니다

대신 개발 메일링 리스트나 IRC 채널로 연락해 주십시오. 기꺼이 도와드리겠습니다! :)

처음 기여할 때 해 볼 만한 작업으로는 다음과 같은 것들이 있습니다.

  • 문서 - PyPy 아키텍처를 이해하는 데 도움이 됩니다
  • 테스트 실패 - nightly builds에서 실패하는 테스트를 찾아 수정하십시오
  • 누락된 언어 기능 - 이 기능들은 저희 issue tracker에 정리되어 있습니다.

소스 관리

PyPy의 주요 git 저장소는 https://github.com/pypy 에서 호스팅되며, 레거시 저장소는 https://foss.heptapod.net/pypy 에서 호스팅됩니다.

PyPy의 레거시 저장소는 Heptapod에서 호스팅됩니다. Heptapod는 Mercurial을 지원하는 GitLab Community Edition의 우호적 포크입니다. https://foss.heptapod.net 은 자유 및 오픈 소스 소프트웨어를 위한 공개 인스턴스입니다(자세한 내용은 여기를 참고하십시오).

이 서비스를 제공해 주신 Octobus와 Clever Cloud에 감사드립니다!

Octobus + Clever Cloud

클론

  • git clone https://github.com/pypy/pypy.git 명령으로 PyPy 저장소를 로컬 컴퓨터에 클론하십시오. 1~2분 정도 걸리지만 한 번만 하면 됩니다. https://pypy.org/download.html#building-from-source 도 참고하십시오.
  • 이제 PyPy 저장소의 전체 사본이 준비되었습니다.

편집

  • 필요한 부분을 수정하십시오. 무엇을 변경했는지 보려면 git diff를 사용하십시오. 새로 추가한 파일(예: 새 테스트 파일)을 git이 인식하게 하려면 git add를 사용하십시오. 그런 파일이 있는지 확인하려면 git status를 사용하십시오. 테스트를 작성하고 실행하십시오! (이 페이지의 나머지 부분을 참고하십시오.)
  • git commit으로 정기적으로 커밋하십시오. 커밋 메시지는 한 줄이어도 괜찮습니다. 저희는 커밋이 많을수록 좋습니다. 조금이라도 진척이 있으면 바로 커밋하십시오. 아직 통과하지 못하는 새 테스트 하나뿐이어도 괜찮고, 모든 테스트가 통과하지 않는 상태에서 무언가를 고친 것이어도 괜찮습니다. 여러분은 한 단계씩 변경 사항의 이력을 쌓아 가고 있으며, 이것이 바로 버전 관리 시스템의 핵심입니다. (git log 같은 명령어도 있으니, 이 이력을 살펴보는 방법을 익히려면 나중에 찾아 읽어 보십시오.)
  • 커밋은 git push를 실행하여 여러분의 포크로 다시 “push”하기 전까지 여러분의 컴퓨터에 남아 있습니다. git push와 git pull 명령은 커밋을 이리저리 복사하며, 그 목적은 관련된 모든 저장소가 최종적으로 정확히 같은 커밋 집합을 갖게 하는 것입니다.
  • 푸시는 자주 하는 것이 좋습니다. 굳이 하지 않을 이유가 없습니다. 위와 같이 설정했다면 푸시하더라도 커밋은 여러분이 이름을 붙인 브랜치에만 들어간다는 점을 기억하십시오. 물론 커밋은 공개적으로 보이지만, 누군가 PyPy의 수많은 브랜치를 돌아다니며 “하, 저 사람 코딩 스타일 좀 봐”라고 말할까 봐 걱정하지 않으셔도 됩니다. 여러분의 작업은 비밀이 아니며 그래도 괜찮다는 마음가짐을 가지도록 노력하십시오. 저희가 여러분의 작업을 있는 그대로 PyPy에 받아들이지 않고 대신 몇 가지를 개선해 달라고 요청할 수는 있지만, 테스트를 작성하지 않는 경우가 아니라면 여러분을 평가하지는 않을 것입니다.

풀 리퀘스트

  • 마지막 단계는 풀 리퀘스트를 여는 것입니다. 그러면 여러분이 해당 브랜치를 원래의 pypy/pypy 저장소에 다시 병합하고 싶어 한다는 것을 저희가 알 수 있습니다. 흥미로운 중간 상태가 있다면 이 작업을 여러 번 해도 됩니다. 하지만 여기까지 오셨다면 저희는 아마 다음 단계로 넘어갈 것입니다. 다음 단계는 바로…
  • 평소의 일상적인 개발에 좀 더 가까이 참여하게 되면, 저희가 보통 작은 변경 사항을 하나 또는 몇 개의 커밋으로 default 또는 py3.9 브랜치에 직접 푸시한다는 것을 알게 될 것입니다. 또한 저희는 실제로 누구에게도 “속하지” 않는 다른 브랜치에서 작업할 때에도 자주 협업합니다. 이 시점에서는 git merge가 필요하며, 두 사람이 같은 브랜치에 서로 다른 커밋을 동시에 푸시하려고 할 때 가끔 발생하는 충돌을 해결하는 방법을 배워야 합니다. 하지만 이는 아마 나중에 다룰 문제일 것입니다 :-)

아키텍처

PyPy는 여러 계층으로 이루어져 있습니다. 오우거나 양파처럼 말입니다. 이러한 계층 덕분에 각 부분을 독립적으로 작업할 수 있을 만큼 충분히 분리해 둘 수 있고, 복잡성도 감당할 수 있는 수준으로 유지할 수 있습니다. 다시 말하지만, 이는 이처럼 복잡한 프로젝트에서 정신 건강을 지키기 위한 최소한의 요건일 뿐입니다. 예를 들어 JIT에 새로운 최적화를 작성할 때는 보통 Python 인터프리터나 JIT 어셈블러 백엔드, 가비지 컬렉터를 전혀 건드릴 필요가 없습니다. 대신 rpython/jit/metainterp/optimizeopt/test/test_*에 작은 테스트를 작성하고 그곳의 파일을 수정하면 됩니다. 그런 다음 PyPy를 컴파일하기만 하면 모든 것이 잘 동작할 것입니다.

더 읽을거리: 아키텍처

어디서부터 시작해야 합니까?

PyPy는 서로 비교적 독립적인 여러 부분으로 이루어져 있습니다. 가장 관심이 가는 부분부터 살펴보기 시작하십시오(모든 경로는 PyPy 최상위 디렉터리를 기준으로 합니다). 디렉터리 참조를 살펴보거나 다음 지점 중 하나에서 시작할 수 있습니다:

빌드

PyPy를 빌드하려면 먼저 미리 빌드된 PyPy를 설치하는 것을 권장합니다(PyPy 다운로드 및 설치 참고). CPython으로도 PyPy를 빌드할 수 있지만, 실행하는 데 훨씬 더 오래 걸립니다. 아키텍처에 따라 두 배에서 세 배 정도 걸립니다.

더 읽을거리: 빌드

코딩 가이드

일반적인 PEP 8 및 서식 표준 외에도, 소스를 살펴보기 전에 알아 두어야 할 중요한 명명 규칙과 코딩 스타일이 여럿 있습니다.

더 읽을거리: 코딩 가이드

테스트

테스트 주도 개발

대신 저희는 테스트 주도 개발을 많이 실천합니다. 이는 한편으로는 컴파일러에 요구되는 품질 수준이 매우 높기 때문이고, 다른 한편으로는 이렇게 복잡한 프로젝트를 제정신을 유지하면서 헤쳐 나갈 다른 방법이 그야말로 없기 때문입니다. 아마 세상에는 테스트 주도 개발이 필요 없을 만큼 똑똑한 사람들도 있겠지만, 저희는 그런 사람들이 아닙니다. pytest는 저희가 테스트에 사용하는 도구이므로 익혀 두는 것을 고려해 보십시오. 저희는 트리 최상위에 직접 수정한 pytest를 함께 제공하므로 python -m pytest는 저희 버전을 사용합니다. 즉, 저희 테스트는 해당 버전의 pytest로 실행되어야 합니다.

또한 extra_tests 디렉터리에는 변환 후 테스트가 있습니다. 이 테스트들은 별도의 디렉터리에서 가상 환경으로 실행되므로 더 최신 버전의 pytest를 사용합니다. 이 테스트들은 가능한 한 CPython에서도 통과하도록 작성되어 있습니다.

PyPy 단위 테스트 실행하기

PyPy 개발은 예나 지금이나 철저하게 테스트 주도로 이루어집니다. 테스트에는 두 가지 방식이 있습니다. 하나는 변환 전에 RPython 위에서 실행되는 테스트(변환되지 않은 테스트)이고, 다른 하나는 변환된 pypy 위에서 실행되는 테스트(앱 테스트)입니다. RPython은 Python2의 방언이므로, 변환되지 않은 테스트는 python2 호스트에서 실행됩니다.

PyPy 소스 트리에는 인라인된 버전의 py.test가 포함되어 있으며, 다음과 같이 입력하여 실행할 수 있습니다:

python2 pytest.py -h

테스트를 성공적으로 실행하려면 build requirements가 필요합니다. 상당수 테스트가 PyPy의 작은 부분들을 컴파일한 다음 그 최소한의 인터프리터 안에서 테스트를 실행하기 때문입니다. cpyext 테스트에는 pycparser도 필요하며, 많은 테스트가 hypothesis로 테스트 케이스를 빌드합니다.

이제 몇 가지 테스트를 실행해 보겠습니다. PyPy에는 여러 가지 테스트 디렉터리가 있으며, 셸 자동 완성을 사용하여 디렉터리나 파일을 지정할 수 있습니다:

python2 pytest.py pypy/interpreter/test/test_pyframe.py

# or for running tests of a whole subdirectory
python2 pytest.py pypy/interpreter/

루트 디렉터리나 최상위 하위 디렉터리인 pypy를 지정해서 “모든” pypy 테스트를 실행하지 않도록 주의하십시오. 몇 시간이 걸리고 RAM을 매우 많이 사용하므로 권장하지 않습니다.

CPython 회귀 테스트를 실행하려면 변환된 PyPy를 준비한 다음, CPython에서와 같은 방식으로 테스트를 실행해야 합니다(아래 참조). 변환 전에 테스트를 실행해 볼 수도 있습니다. 다만 이 방법은 모든 경우에 동작하지는 않는 편법(hack)을 사용하며 대개 매우 느리다는 점에 유의하십시오: py.test lib-python/2.7/test/test_datetime.py. 보통은 실패하는 테스트를 길어야 몇 줄 정도의 최소한의 형태로 추출하여 pypy/*/test/에 있는 자체 테스트 중 하나에 넣는 것이 더 좋은 방법입니다.

앱 레벨 테스트

일반적으로 python2 pytest.py를 호출하면 python2 위에서 실행되는 변환되지 않은 PyPy에서 애플리케이션 수준 테스트가 실행되지만, 호스트 Python에서 직접 테스트를 실행할 수 있는 테스트 확장도 있습니다. 이는 cpyext 같은 모듈에서 CPython과 PyPy의 테스트 결과를 비교하고 대조할 때 매우 편리합니다.

애플리케이션 수준 테스트(파일 이름이 test_가 아니라 apptest_로 시작하는 테스트)는 pytest에 -D 또는 --direct-apptest를 전달하면 호스트 인터프리터에서 직접 실행됩니다.:

pypy3 -m pytest -D pypy/interpreter/test/apptest_pyframe.py

혼합 수준 테스트(test_로 시작하는 일반적인 테스트)는 pytest에 -A 또는 --runappdirect 옵션을 지정하여 실행합니다.:

python2 pytest.py -A pypy/module/cpyext/test

여기서 python2는 python2 또는 pypy2 중 하나일 수 있습니다. py3* 브랜치 중 어느 것에서든, 변환되지 않은 테스트가 다음 명령으로 실행되도록 수집 단계를 python2로 실행해야 합니다:

python2 pytest.py -A pypy/module/cpyext/test --python=path/to/pypy3

변환 후 테스트

변환을 실행하면 변환을 실행한 디렉터리에 pypy-c(Python3 브랜치의 경우 pypy3-c)라는 이름의 바이너리가 생성됩니다.

표준 CPython 회귀 테스트 스위트의 테스트를 실행하려면 일반적인 Python 방식을 사용하십시오. 즉, 다음과 같이 실행합니다(정확한 바이너리 이름을 사용하십시오).:

./pypy3-c -m test.test_datetime
# or
./pypy3-c lib-python/3/test/test_audit.py

Buildbot

PyPy는 https://buildbot.pypy.org 에서 buildbot 기반 CI 시스템을 운영합니다. 이 시스템은 https://foss.heptapod.net/pypy/buildbot 에 있는 코드로 구동됩니다. x86_64, i686, aarch64 환경의 Linux 러너는 의존성을 관리하는 docker 컨테이너를 사용합니다. 자세한 내용은 Dockerfile을 참고하십시오. Windows 러너는 externals 저장소의 win64_14x 브랜치에 있는 의존성을 사용합니다. macOS 러너(x86_64, arm64)는 M1 머신에서 venv를 사용합니다.

도구 및 유틸리티

PyPy Python 인터프리터의 내부 동작에 관심이 있으시다면, 변환되지 않은 Python 인터프리터에는 그 내부를 들여다볼 수 있게 해 주는 몇 가지 기능이 있습니다.

인터프리터 수준 콘솔

PyPy로 Python 인터프리팅을 시작하려면 distutils가 지원하는 C 컴파일러를 설치하고, Python 2.7 이상으로 PyPy를 실행하십시오:

cd pypy
python bin/pyinteractive.py

몇 초가 지나면(이것은 CPython 위에서 실행된다는 점을 기억하십시오) PyPy 프롬프트가 나타납니다. PyPy 프롬프트는 Python 프롬프트와 같지만 “>”가 하나 더 붙어 있습니다.

콘솔에서 <Ctrl-C>를 누르면 일반적인 CPython 콘솔인 인터프리터 수준 콘솔로 들어갑니다. 그러면 PyPy의 내부 객체(예: 객체 공간(object space))와 PyPy 프롬프트에서 만든 모든 변수에 w_ 접두사를 붙여 접근할 수 있습니다.:

>>>> a = 123
>>>> <Ctrl-C>
*** Entering interpreter-level console ***
>>> w_a
W_IntObject(123)

이 메커니즘은 양방향으로 동작합니다. 인터프리터 수준에서 w_ 접두사를 붙여 변수를 정의하면 애플리케이션 수준에서 그 변수를 볼 수 있습니다:

>>> w_l = space.newlist([space.wrap(1), space.wrap("abc")])
>>> <Ctrl-D>
*** Leaving interpreter-level console ***

KeyboardInterrupt
>>>> l
[1, 'abc']

인터프리터 수준 콘솔은 CPython 수준에서 실행되므로 프롬프트가 ‘>>>’뿐이라는 점에 유의하십시오. PyPy로 돌아가려면 <Ctrl-D>(Linux) 또는 <Ctrl-Z>, <Enter>(Windows)를 누르십시오.

또한 이 모드에서는 모든 모듈이 기본적으로 제공되지는 않으므로(예: greenlet에 필요한 _continuation), --withmod-... 명령줄 옵션 중 하나를 사용해야 할 수도 있습니다.

인터프리터 수준과 애플리케이션 수준 사이의 구분에 대해 더 읽어 보시는 것도 좋습니다.

pyinteractive.py 옵션

PyPy 인터프리터의 명령줄 옵션 목록을 보려면 다음과 같이 입력하십시오:

cd pypy
python bin/pyinteractive.py --help

pyinteractive.py는 pyinteractive.py를 사용자 설정하는 데 쓸 수 있는 많은 옵션 외에도 CPython이 지원하는 옵션 대부분을 지원합니다. 명령줄에서 PyPy를 사용하는 예로, 다음과 같이 입력할 수 있습니다.:

python pyinteractive.py --withmod-time -c "from test import pystone; pystone.main(10)"

또는 일반 Python에서와 마찬가지로 명령줄에 스크립트 이름을 지정하기만 하면 됩니다:

python pyinteractive.py --withmod-time ../../lib-python/2.7/test/pystone.py 10

--withmod-xxx 옵션은 내장 모듈 xxx를 활성화합니다. 기본적으로는 내장 모듈을 초기화하는 데 시간이 걸리기 때문에 거의 모든 내장 모듈이 비활성화되어 있습니다. 그래도 모든 내장 모듈을 활성화하고 싶다면 --allworkingmodules를 사용할 수 있습니다.

모든 명령줄 옵션이 각각 어떤 역할을 하는지 자세히 알아보려면 설정 섹션을 참조하십시오.

바이트코드와 객체에 대한 연산 트레이싱

간단한 트레이싱 모드를 사용하여 바이트코드 해석 과정을 모니터링할 수 있습니다. 이 모드를 활성화하려면 대화형 PyPy 콘솔에서 __pytrace__ = 1을 설정하십시오.:

>>>> __pytrace__ = 1
Tracing enabled
>>>> x = 5
        <module>:           LOAD_CONST    0 (5)
        <module>:           STORE_NAME    0 (x)
        <module>:           LOAD_CONST    1 (None)
        <module>:           RETURN_VALUE    0
>>>> x
        <module>:           LOAD_NAME    0 (x)
        <module>:           PRINT_EXPR    0
5
        <module>:           LOAD_CONST    0 (None)
        <module>:           RETURN_VALUE    0
>>>>

데모

example-interpreter 저장소에는 RPython 변환 툴체인으로 작성한 예제 인터프리터가 들어 있습니다.

흐름 그래프(flow graph)를 보기 위한 graphviz와 pygame(적극 권장)

생성된 흐름 그래프(flow graph)를 보려면 graphviz와 pygame이 모두 필요합니다: