pyinstaller 란..
python 파일을 패키징해서 executable 형식으로 만들어주는 아주 좋은 도구이다.
사용방법이 굉장히 간단해 보이지만, 실제로 적용하고 세부 설정을 하다보니 너무 할게 많았고 디버깅이 잘 되지 않아 삽질을 많이 했다. 심지어 포스팅 된 글들도 상대적으로 적어서..
일단 필수적으로 알고 가야할게 있다.
window / Linux 둘다 pyinstaller를 사용할 수 있다!
다만 당연한 이야기일 수 있지만, window에서 빌드한것은 linux 에서 사용할 수 없고, linux에서 빌드한것은 windows 에서 사용할 수 없다. (java gradle로 war파일을 만들면 window, linux 상관없이 동작하는거에 비해 불편하긴 하다.)
설치
pip install pyinstaller
내 환경
OS: windows 10 / Linux Ubuntu 20.04
Python Version : 3.8x (python venv 사용)
빌드대상파일: main.py
목적: FastAPI Server 를 exe파일로 만들어 배포하기
사용방법
Pyinstaller doc: https://pyinstaller.readthedocs.io/en/stable/
pyinstaller [option] [filename.py]
# file name: main.py 인 경우
pyinstaller main.py
사실 사용 방법은 굉장히 간단하다.
하지만 정말 제대로 사용하려면 빌드 후 생성되는 main.spec 파일을 잘 뜯어 고쳐야 한다.
잘 고친 후에는
pyinstaller main.spec
이렇게 옵션이 적용 된 spec 파일로 빌드를 해줘야 한다.
이제부터 빌드하면서 생긴 오류.. 삽질을 공유하겠다.
삽질1
main.spec 파일로 계속 빌드를 했지만, 옵션이 적용되지 않는 문제
이 경우는 내가 멍청해서 일어난 일이다.
예를들어
pyinstaller -D 옵션은 빌드한 파일을 하나의 Directory 안에 넣고 그 안에 여러개의 필요한 파일들이 들어있게된다.
pyinstaller -F 옵션은 하나의 exe파일만 떨어지므로 굉장히 간편하게 보이지만 어떤 특정 파일을 참조하는 코드가 있다면 골치아파진다.
그런데 이 옵션은 main.py 에 우선적으로 적용되고, main.spec은 옵션을 무시한다.
(정확하게 말하면 spec안에 있는 값만 참조함)
# pyinstaller -F main.py
를 사용해서 빌드한 exe 파일이 존재하고 이에 맞는 main.spec이 생성되어 spec파일을 수정한 뒤
# pyinstaller -D main.spec
을 아무리 해봐야, -D 옵션은 적용되지도 않고 무시된다.
따라서 다시 -D옵션으로 생성하고 싶다면
# pyinstaller -D main.py 를 다시 써주어 적용해야 한다.
당연한 이야기 일 수 있지만, .... 오류하나 안나기 때문에 엄청 답답했다... (pyinstaller 지우고 다시깔아보기도했음 ㅠ)
삽질2
Python lib가 제대로 import 되지 않는 문제 (no module name 'lib')
분명 pip freeze 로 lib확인도 해보고 pyinstaller 도 다시 해보고.. python main.py 로 실행하면 잘 되고...
왜 module을 찾을 수 없다는걸까 했는데,
pyinstaller는 특정 모듈을 import하지 못하는경우가 있다. 이 에러는 흔한 문제인지 검색하니 바로 나왔다.
내 경우에 uvicorn 모듈을 인식하지 못해서 문제가 생겼었는데,
해결방법은 main.spec 파일 내부에 hiddenimports =[] 이 공간에 uvicorn 관련 모듈을 다 박아주면 된다! (필요한거만 박으면됨..)
삽질3
python 코드 내에서 특정 data를 참조해야 하는데 찾지 못하는 경우
pyinstaller는 불친절하게도 python 코드 내부에서 참조하는 데이터들을 전혀 고려해주지 않는다.
이 문제는 다른 사람들을 보면 보통 icon 파일을 넣었는데 적용이 안된다던지,
ui를 위해 이미지를 넣었는데 적용이 안된다던지 그런 경우가 많았다.
내 경우엔 File 입출력기능과, 개인적인 dictionary를 참조한다던가,
나중에 포스팅하겠지만 conf.ini 파일을 생성하여 환경변수를 빌드 후에도 변경 할 수 있게 한다던가 했기 때문에 사용하였다.
해결방법!
일단 ./dist/main 폴더를 기본 경로로 잡아주는게 중요하다.
# 내 환경 DIRECTORY 구조
# ./dist/main/main.py
# ./dist/main/common/const.py # 환경설정 변수들이 들어있는 파일
# python source code 내에서 현재 파일의 위치를 가져옴
(os.path.dirname(os.path.abspath(__file__))
# 내 경우. const.py 위치를 기준으로 BASE_DIR를 설정
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # ./dist/main
# 참조할 파일
KEYWORD_DIC = f'{BASE_DIR}/common/dic.txt'
위 소스코드 처럼 BASE_DIR를 지정해 주고 main.spec 파일에서 datas = [] 부분을 수정해주면 빌드 후에도 파일을 참조할 수 있다.
※ 주의
빌드시 -F 옵션으로 빌드하게 되면 하나의 .exe 파일만 떨어지게 되는데 이 경우에 ubuntu 환경에서는
BASE_DIR가 /tmp/_MEIxxxxxx/ 로 지정되고 사용되기 때문에 파일을 정상적으로 읽어오지 못한다.
그래서 나는 반 강제적으로 일단 -D로 빌드하긴 했는데, 분명 다른 방법이 있을거라 생각한다.. 댓글로 알려주시면..
삽질4
Linux 환경에서는 빌드 후 main 실행파일이 생성되는데, 이 파일의 형식은 application/x-executable 이다.
바로 실행이 안되기 때문에 권한 설정이 필요하다.
# main 파일 권한 변경
> sudo chmod 777 ./main
# 실행
> ./main
사실 나중엔 root 아래 파일도 사용하게 되어서 지금은
> sudo ./main
으로 실행한다....
삽질5
UPX is not abailable (UPX - pyinstaller를 좀 더 빠르게 도와주는 친구)
정확하게 어떤 상황에서 이 에러가 갑자기 뜨는지 모르겠지만, 가끔 UPX is not available 이라는 ERROR가 뜰 때가 있다.
경험상 보통 main.spec 파일이 초기화 된 경우에 이런 에러가 나타났는데 main.spec 내부에 upx=True 를 False로 바꿔주어도 되고
아래 링크를 참조하여 UPX를 따로 다운로드 후 지정해주어도 된다.
https://www.tutorialexample.com/fix-pyinstaller-upx-is-not-available-error-pyinstaller-tutorial/
사실 난 그냥 main.spec을 백업해 두고, 이 에러가 떴을때 덮어쓰기 해버렸다. 제일편하다.
삽질6
failed to execute script
exe파일을 실행했을 때, 이 에러가 뜨면 또 지긋지긋한 경로문제가 발생한 것이다.
위 삽질3을 참조하여 해결할 수 있다..!!
삽질7
uvicorn 실행 오류. Error loading ASGI app. Could not import module "main".
이 또한 경로문제리...
일반적으로 uvicorn을 실행하는 명령어는 아래와 같다.
app = create_app()
if __name__ == "__main__":
uvicorn.run("main:app", host="0.0.0.0", port=8002, reload=False)
하지만 쉽게 될리가 없지
python main.py로 실행하면 잘 동작하지만,
pyinstaller로 빌드 후 ./main 하면 Could not import module "main". 같은 오류가 뜬다.
main.py를 아래처럼 수정 후 빌드하면 정상 동작한다.
app = create_app()
def serve():
uvicorn.run(app, host=APP_HOST_ADD, port=APP_PORT)
if __name__ == "__main__":
serve()
혹시 또 추가하게 될 삽질이 있다면,, 공유하겠다..
'공부 > Python' 카테고리의 다른 글
[python] pyinstaller jinja2.PackageLoader Error (0) | 2021.06.24 |
---|---|
[python] pyinstaller 와 빌드 후 ini 환경변수 읽기(konfig) (1) | 2021.06.19 |
[python] 클래스를 함수처럼 사용할수 있게.. __call__함수 (0) | 2021.06.15 |
[Python] @Decorator 파이썬 @데코레이터 (2) | 2021.06.11 |
[python] opencv를 활용한 이미지에서 표 객체 찾기(table detection) (0) | 2021.05.31 |
댓글