Excel 설정 테이블을 JSON, XML, Lua 형식의 구성 파일로 변환하는 소규모 도구를 개발했다. 최근 Egret 프레임워크를 학습하면서 구성 파일 변환 기능이 필요해졌고, 이 도구를 활용하게 되었다. 하지만 작업 디렉토리에 도구를 복사하려는 순간 문제가 발생했다. 확장성을 고려해 프로그램을 너무 세부적으로 분할해 놓은 상태였다.
user@machine:~/Documents/projects/excel_converter$ ls -lh
총 80K
drwxrwxr-x 2 user user 4.0K 7월 27 23:03 bin
drwxrwxr-x 2 user user 4.0K 7월 27 23:03 client
-rw-rw-r-- 1 user user 7.7K 7월 27 23:03 parser.py
-rw-rw-r-- 1 user user 893 7월 27 23:03 exceptions.py
-rw-rw-r-- 1 user user 16K 7월 27 23:03 sample.xlsx
-rw-rw-r-- 1 user user 131 7월 27 23:03 startup.bat
-rw-rw-r-- 1 user user 127 7월 27 23:03 startup.sh
-rw-rw-r-- 1 user user 3.9K 7월 27 23:03 processor.py
-rw-rw-r-- 1 user user 705 7월 27 23:03 processor.spec
-rw-rw-r-- 1 user user 2.4K 7월 27 23:03 README.md
drwxrwxr-x 2 user user 4.0K 7월 27 23:03 server
-rw-rw-r-- 1 user user 4.0K 7월 27 23:03 json_generator.py
-rw-rw-r-- 1 user user 7.2K 7월 27 23:03 lua_generator.py
-rw-rw-r-- 1 user user 5.6K 7월 27 23:03 xml_generator.py
이처럼 많은 파일들을 작업 디렉토리에 배치하면 관리가 어렵고, 프로젝트 소스 코드와 혼동될 수 있다. VS Code에서도 프로젝트 내 파일과 외부 파일을 구분하기 어려운 상황이다. 게다가 기획자들이 사용해야 하므로 Python과 openpyxl 라이브러리를 설치하는 배포 과정이 복잡하다. 따라서 Python 스크립트를 하나의 실행 파일로 패키징하는 방법을 시도하기로 했다.
Google 검색 결과, 일반적으로 pyinstaller와 py2exe 두 가지 도구가 사용된다. 두 도구의 기능은 비슷하지만, pyinstaller는 --onefile 매개변수를 제공하여 모든 스크립트를 단일 실행 파일로 패키징할 수 있어 원하는 기능에 적합했다. 설치 및 시도 과정은 다음과 같다:
excel_converter$pip install pyinstaller
excel_converter$pyinstaller -F processor.py
...
tuple index out of range
설치는 성공적이었지만 패키징 과정에서 "tuple index out of range" 오류가 발생했다. "pyinstaller tuple index out of range"로 검색한 결과, GitHub 이슈에서 pyinstaller 3.2.1 버전이 Python 3.6.1과 호환되지 않는 문제를 발견했다. 개발 버전에서는 수정되었다는 답변을 확인했지만, README를 확인해보니 OS X, Linux, Windows 세 플랫폼의 CI 상태가 모두 실패였다.
불필요한 삽질을 피하고자 로컬 Python 버전을 3.6.1에서 3.5로 다운그레이드하고 pyinstaller를 재설치한 후, "pyinstaller -F processor.py" 명령으로 패키징을 수행했다. 성공적으로 실행되어 dist 디렉토리에 processor.exe 파일이 생성되었다. 실행 파일을 테스트해 본 결과 다음과 같은 오류가 발생했다:
Traceback (most recent call last):
File "processor.py", line 96, in <module>
config.timeout, config.extension, config.server_output, config.client_output )
File "processor.py", line 25, in __init__
self.server_output = importlib.import_module( "xml_generator" )
File "importlib\__init__.py", line 126, in import_module
File "<frozen importlib._bootstrap>", line 986, in _gcd_import
File "<frozen importlib._bootstrap>", line 969, in _find_and_load
File "<frozen importlib._bootstrap>", line 956, in _find_and_load_unlocked
ImportError: No module named 'xml_generator'
Failed to execute script processor
xml_generator 모듈이 누락되었음을 알 수 있었다. 이는 Excel을 Lua 형식의 설정 파일로 변환하는 사용자 정의 모듈이다. PyInstaller는 스크립트 의존성 모듈을 분석하지만, 본 프로그램은 실행 시간에 전달된 매개변수에 따라 동적으로 모듈을 로드한다. 사용자가 Excel을 어떤 형식의 파일로 변환할지 미리 알 수 없기 때문이다. 모든 모듈을 로드하는 방안도 있지만, 기존 설계 방침에 어긋난다. 즉, 모듈 인터페이스를 정의해 두어 새로운 모듈 추가 시 기존 코드를 수정하지 않아도 자동으로 로드되도록 설계되어 있다.
검색 결과 유사한 사례를 찾지 못했지만, PyInstaller 매뉴얼(https://pythonhosted.org/PyInstaller/spec-files.html)을 통해 spec 구성 파일을 사용하여 아이콘, 사용자 정의 바이너리 파일 등 다양한 데이터를 패키징할 수 있음을 알게 되었다. http://pythonhosted.org/PyInstaller/when-things-go-wrong.html#listing-hidden-imports 및 http://pythonhosted.org/PyInstaller/hooks.html#understanding-pyinstaller-hooks 문서에서는 hiddenimports 옵션을 사용하여 숨겨진 모듈을 가져올 수 있다고 설명하고 있다.
PyInstaller 패키징 과정을 확인해보면, "pyinstaller -F processor.py" 명령 실행 시 현재 디렉토리에 processor.spec 파일이 생성된다:
# -*- mode: python -*-
block_cipher = None
a = Analysis(['processor.py'],
pathex=['E:\\share\\projects\\excel_converter'],
binaries=[],
datas=[],
hiddenimports=[],
hookspath=[],
runtime_hooks=[],
excludes=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher)
pyz = PYZ(a.pure, a.zipped_data,
cipher=block_cipher)
exe = EXE(pyz,
a.scripts,
a.binaries,
a.zipfiles,
a.datas,
name='processor',
debug=False,
strip=False,
upx=True,
console=True )
hiddenimports 항목에 동적으로 로드되는 모듈을 추가하여 hiddenimports=['xml_generator','lua_generator','json_generator']로 변경한 후 재패키징한다. 이때 "pyinstaller -F processor.py" 명령을 다시 실행해서는 안 된다. 해당 명령은 수정한 내용을 덮어쓰는 새 spec 파일을 생성하기 때문이다. 반드시 "pyinstaller processor.spec" 명령을 사용하여 패키징해야 한다.
동적 로드 모듈을 추가한 후, 전체 실행 파일 크기는 7MB 이상이며 정상적으로 작동한다. 하지만 64비트 시스템에서 패키징된 프로그램은 32비트 환경에서 실행할 수 없다는 제약이 있다. 전체 도구는 GitHub 저장소에 공개되어 있다: https://github.com/changnet/excel_converter