기여 가이드라인¶
Contents
PyPy는 뛰어들기 어렵다는 평판을 가진 매우 큰 프로젝트입니다. 이런 평판 중 일부는 정당하고, 일부는 순전히 우연입니다. 기여하고자 하는 모든 사람이 배워야 할 세 가지 중요한 교훈이 있습니다:
- PyPy는 계층을 가지고 있습니다. 서로 매우 잘 분리된 여러 아키텍처 조각들이 있습니다. 이에 대해서는 아래에서 더 다루겠지만, 종종 이것이 나타나는 방식은 예상했던 것과 다른 계층에 무언가가 위치하는 형태로 드러납니다. 예를 들어 JIT 구현을 찾고 있다면, 파이썬 프로그래밍 언어의 구현 부분에서는 찾을 수 없을 것입니다.
- 위와 같은 이유로, 저희는 테스트 주도 개발(Test Driven Development)을 매우 중요하게 생각합니다. 이는 저희가 믿는 방식일 뿐만 아니라, PyPy의 아키텍처 자체가 TDD를 염두에 두었을 때는 매우 잘 작동하지만 그렇지 않을 때는 그다지 잘 작동하지 않기 때문이기도 합니다. 개발은 흔히 서로 관련 없는 한쪽 구석에서 유닛 테스트를 하나씩 진행하다가, 이후 커다란 스위치를 전환하여 이를 한데 모으는 방식으로 이루어집니다. (대개는 별다른 조정 없이도 바로 작동합니다. 그렇지 않다면, 유닛 테스트를 충분히 작성하지 않은 것입니다.) 다시 한번 강조할 가치가 있습니다 - TDD를 수행한다면 PyPy의 접근 방식은 훌륭하지만, 그렇지 않다면 그다지 좋지 않습니다.
- 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은 자유 및 오픈소스 소프트웨어(Free and Open-Source Software)를 위한 공개 인스턴스입니다(자세한 정보는 여기를 참조하십시오).
이 서비스를 제공해 주신 Octobus와 Clever Cloud에 감사드립니다!
클론(Clone)¶
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같은 명령어들이 있습니다.)- 커밋들은 여러분의 포크로 다시 “push”하기 위해
git push를 실행하기 전까지는 로컬 컴퓨터에 남아 있습니다.git push와git pull명령은 커밋들을 이곳저곳으로 복사하며, 그 목표는 관련된 모든 저장소가 완전히 동일한 커밋 집합을 갖도록 하는 것입니다. - 자주 푸시하는 것이 좋습니다. 그렇게 하지 않을 특별한 이유는 없습니다. 위와 같이 설정하면, 커밋을 푸시하더라도 여러분이 지정한 브랜치에만 존재한다는 것을 기억하십시오. 맞습니다, 그 커밋들은 공개적으로 보이지만, 누군가 PyPy의 수많은 브랜치를 돌아다니며 “저 사람 코딩 스타일 형편없네”라고 말할까 봐 걱정할 필요는 없습니다. 여러분의 작업이 비밀이 아니며 그래도 괜찮다는 마음가짐을 가지려 노력하십시오. PyPy 프로젝트에서는 있는 그대로 받아들이지 않고 몇 가지 개선을 요청할 수도 있지만, 테스트를 작성하지 않는 경우가 아니라면 여러분을 평가하지는 않을 것입니다.
풀 리퀘스트¶
- 마지막 단계는 풀 리퀘스트를 여는 것으로, 이를 통해 여러분이 해당 브랜치를 원본
pypy/pypy저장소로 다시 병합하고 싶다는 것을 알 수 있게 합니다. 흥미로운 중간 상태가 있다면 이 작업을 여러 번 수행할 수도 있지만, 거기까지 도달하면 다음 단계로 진행할 가능성이 높습니다. 그 다음 단계는… - 일상적인 개발 작업에 좀 더 가까이 다가가 보면, 저희가 보통 작은 변경 사항들을
default나py3.9브랜치에 하나 또는 몇 개의 커밋으로 직접 푸시(push)한다는 것을 알게 될 것입니다. 또한, 저희는 실제로 누구에게도 “속하지” 않는 다른 브랜치에 있을 때도 자주 협업합니다. 이 시점에서git merge가 필요하며, 두 사람이 같은 브랜치에 서로 다른 커밋을 병렬로 푸시하려고 할 때 가끔 발생하는 충돌을 해결하는 방법을 배우게 될 것입니다. 하지만 그것은 아마도 나중에 다룰 문제일 것입니다:-)
아키텍처¶
PyPy에는 계층이 있습니다. 오거나 양파처럼요. 이러한 계층은 각 부분을 독립적으로 작업할 수 있을 만큼 충분히 분리된 상태로 유지하고, 복잡성을 관리 가능하게 만드는 데 도움이 됩니다. 이는, 다시 말하지만, 이렇게 복잡한 프로젝트에서는 당연한 요구 사항일 뿐입니다. 예를 들어 JIT를 위한 새로운 최적화를 작성하는 것은 보통 Python 인터프리터, JIT 어셈블러 백엔드, 가비지 컬렉터를 전혀 건드리는 것과관계가 없습니다. 대신 rpython/jit/metainterp/optimizeopt/test/test_*에 작은 테스트를 작성하고 그곳의 파일들을 수정해야 합니다. 그 후에는 PyPy를 컴파일하기만 하면 되고, 모든 것이 잘 작동할 것입니다.
더 읽을거리: 아키텍처
어디서부터 시작할까요?¶
PyPy는 서로 비교적 독립적인 부분들로 구성되어 있습니다. 가장 관심이 가는 부분부터 살펴보시면 됩니다 (모든 경로는 PyPy 최상위 디렉터리를 기준으로 합니다). 저희 디렉터리 참조를 살펴보시거나 다음 항목들 중 하나로 시작하실 수 있습니다:
- pypy/interpreter는 바이트코드 인터프리터를 담고 있습니다: 바이트코드 디스패처는 pypy/interpreter/pyopcode.py에, 프레임과 코드 객체는 pypy/interpreter/eval.py와 pypy/interpreter/pyframe.py에, 함수 객체와 인자 전달은 pypy/interpreter/function.py와 pypy/interpreter/argument.py에, 객체 공간(object space) 인터페이스 정의는 pypy/interpreter/baseobjspace.py에, 모듈은 pypy/interpreter/module.py와 pypy/interpreter/mixedmodule.py에 있습니다. 바이트코드 인터프리터를 지원하는 핵심 타입들은 pypy/interpreter/typedef.py에 정의되어 있습니다.
- pypy/interpreter/pyparser는 다양한 Python 버전의 문법을 파싱할 수 있게 해주는 재귀 하강 파서와 문법 파일을 포함합니다. 문법이 처리되고 나면, 파서는 위의 메커니즘을 통해 효율적인 코드로 번역(translation)될 수 있습니다.
- pypy/interpreter/astcompiler는 컴파일러를 포함합니다. 이는 CPython의 compiler 패키지를 수정한 버전을 포함하고 있으며, 몇 가지 버그를 수정했고 번역(translation)이 가능합니다.
- pypy/objspace/std는 표준 객체 공간(Standard object space)를 포함합니다. 주 파일은 pypy/objspace/std/objspace.py입니다. 각 타입에 대해,
xxxobject.py파일은 1차 근사로서 타입xxx의 객체에 대한 구현을 담고 있습니다. (일부 타입은 여러 구현을 가지고 있습니다.)
빌드¶
PyPy를 빌드하려면, 먼저 미리 빌드된 PyPy를 설치하는 것을 권장합니다 (PyPy 다운로드 및 설치 참조). CPython으로 PyPy를 빌드하는 것도 가능하지만, 실행하는 데 훨씬 더 오래 걸립니다 – 아키텍처에 따라 두 배에서 세 배 정도 더 걸립니다.
더 읽을거리: 빌드
테스팅¶
테스트 주도 개발¶
대신, 우리는 테스트 주도 개발을 많이 실천합니다. 이는 부분적으로는 컴파일러에 대한 매우 높은 품질 요구사항 때문이고, 부분적으로는 이렇게 복잡한 프로젝트를 제정신을 유지하며 헤쳐나갈 다른 방법이 전혀 없기 때문입니다. 이것이 필요 없을 만큼 똑똑한 사람들이 아마 어딘가에 있겠지만, 우리는 그런 사람들이 아닙니다. 우리가 테스트에 사용하는 도구이므로, 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 위에서 실행되는, 번역(translation)되지 않은 PyPy에서 앱 레벨 테스트가 실행되지만, 저희는 호스트 python에서 직접 테스트를 실행할 수 있는 테스트 확장을 가지고 있습니다. 이는 CPython과 PyPy 간 테스트 결과를 비교하고 대조하는 데 있어 cpyext와 같은 모듈에 매우 편리합니다.
앱 레벨(App-level) 테스트(파일 이름이 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* 브랜치 중 어느 것에서든, 번역되지 않은(untranslated) 테스트가 다음으로 실행되도록 수집(collection) 단계는 python2로 실행해야 합니다::
python2 pytest.py -A pypy/module/cpyext/test --python=path/to/pypy3
변환 후 테스트하기¶
변환을 실행하면 변환을 실행한 디렉터리에 pypy-c(Python3 브랜치의 경우 pypy3-c)라는 이름의 바이너리가 생성됩니다.
표준 CPython 회귀 테스트 스위트에서 테스트를 실행하려면, 일반적인 파이썬 방식을 사용합니다. 즉(정확한 바이너리 이름을 사용합니다)::
./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 인터프리터의 내부 동작에 관심이 있으시다면, 번역(translation)되지 않은 Python 인터프리터에는 내부를 살펴볼 수있게 해주는 몇 가지 기능이 있습니다.
인터프리터 레벨 콘솔¶
PyPy로 Python 인터프리팅을 시작하려면, distutils가 지원하는 C 컴파일러를 설치하고 PyPy를 실행하기 위해 Python 2.7 이상을 사용해야 합니다.:
cd pypy
python bin/pyinteractive.py
몇 초가 지나면(참고로, 이는 CPython 위에서 실행되는 것입니다) 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>(리눅스의 경우) 또는 <Ctrl-Z>, <Enter>(윈도우의 경우)를 누르시기 바랍니다.
또한 이 모드에서는 기본적으로 모든 모듈을 사용할 수 있는 것은 아니므로(예: greenlet에 필요한 _continuation), --withmod-... 명령줄 옵션 중 하나를 사용해야 할 수도 있습니다.
인터프리터 수준과 앱 수준의 구분에 대해 더 알아보고 싶으시다면 interpreter-level and app-level을 참고하시기 바랍니다.
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이 모두 필요합니다:
graphviz: https://www.graphviz.org/Download.php