問題
最近在專案裡用到了pikepdf這個庫,用於實現pdf水印插入的一個小功能,原始碼除錯階段執行一切OK但是在出包時報瞭如下異常。
Traceback (most recent call last):
File "pikepdf\__init__.py", line 19, in <module>
File "PyInstaller\loader\pyimod03_importers.py", line 495, in exec_module
File "pikepdf\_version.py", line 13, in <module>
File "importlib\metadata.py", line 530, in version
File "importlib\metadata.py", line 503, in distribution
File "importlib\metadata.py", line 177, in from_name
importlib.metadata.PackageNotFoundError: pikepdf
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "main.py", line 1, in <module>
File "PyInstaller\loader\pyimod03_importers.py", line 495, in exec_module
File "pikepdf\__init__.py", line 21, in <module>
ImportError: Failed to determine version
[29708] Failed to execute script 'main' due to unhandled exception!
異常定位
列印了兩份堆疊的資訊,翻翻炸出來點 init.py 的21行,程式碼如下
try:
from ._version import __version__
except ImportError as _e: # pragma: no cover
raise ImportError("Failed to determine version") from _e
從.version.py檔案匯入__version__失敗?看看_version.py
try:
from importlib_metadata import version as _package_version # type: ignore
except ImportError:
from importlib.metadata import version as _package_version
__version__ = _package_version('pikepdf')
__all__ = ['__version__']
再看看上面的異常,也就再_package_version這個函式了。這邊可以先寫個簡單的demo.py驗證下,使用pyinstgaller編譯後執行。
# demo.py
import pikepdf
if __name__ == '__main__':
print("Hello World")
# 輸出
Traceback (most recent call last):
File "pikepdf\__init__.py", line 19, in <module>
File "PyInstaller\loader\pyimod03_importers.py", line 495, in exec_module
File "pikepdf\_version.py", line 13, in <module>
File "importlib\metadata.py", line 530, in version
File "importlib\metadata.py", line 503, in distribution
File "importlib\metadata.py", line 177, in from_name
importlib.metadata.PackageNotFoundError: pikepdf
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "main.py", line 1, in <module>
File "PyInstaller\loader\pyimod03_importers.py", line 495, in exec_module
File "pikepdf\__init__.py", line 21, in <module>
ImportError: Failed to determine version
[29708] Failed to execute script 'main' due to unhandled exception!
符合預期。所以說importlib.metadata.version 無法在pyinstaller打包後執行?
問題原因
對於pkgs_to_check_at_runtime中列出的每個包,需要通過在spec檔案中使用copy_metadata(name)來收集後設資料。說白了就是pyinstaller打包後缺少對應的metadata資訊。
修復方案
1. 降低版本pikepdf的版本
這個庫以前用過,沒有出這個么蛾子。看了下舊版本的原始碼,一下是5.1.3版本之前的get_version實現,沒有使用importlib庫,自然也不會有問題。
from pkg_resources import DistributionNotFound
from pkg_resources import get_distribution as _get_distribution
try:
__version__ = _get_distribution(__package__).version
except DistributionNotFound: # pragma: no cover
__version__ = "Not installed"
__all__ = ['__version__']
2. 在pyinstaller打包時指定copy-metadata
像這樣
pyinstaller -F --copy-metadata pikepdf main.py
碎碎念
我覺得完全可以像版本5.1.3之前一樣,獲取不到時賦值為"Not installed"一樣就行。然後提了個pr[https://github.com/pikepdf/pikepdf/pull/358]給作者,可惜作者認為這是importlib的鍋(不改),倒也時說得過去。