首页 > 解决方案 > Matplotlib“无法确定主目录”问题在 MSYS2/MinGW 中使用 PyInstaller

问题描述

这比帖子的标题可能暗示的要复杂一些。问题源于我有一个用 C 语言编写的大型 GTK 应用程序,它生成了一个 Python matplotlib 进程,该进程使用 Python GTK 库嵌入到 GTK 窗口中。matplotlib 脚本使用 PyInstaller 编译成可执行文件。在 Linux 环境(Ubuntu 16.04/18.04)中一切正常,但在尝试使用 MSYS2/MinGW 为 Windows 编译应用程序时情况并非如此。当我在 Windows 中编译应用程序时,绘图失败并出现以下错误:

Traceback (most recent call last):
  File "plotter.py", line 6, in <module>
  File "<frozen importlib._bootstrap>", line 991, in _find_and_load
  File "<frozen importlib._bootstrap>", line 975, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 671, in _load_unlocked
  File "PyInstaller/loader/pyimod03_importers.py", line 531, in exec_module
  File "matplotlib/__init__.py", line 921, in <module>
  File "matplotlib/__init__.py", line 602, in matplotlib_fname
  File "matplotlib/__init__.py", line 599, in gen_candidates
  File "matplotlib/__init__.py", line 239, in wrapper
  File "matplotlib/__init__.py", line 502, in get_configdir
  File "matplotlib/__init__.py", line 444, in _get_xdg_config_dir
  File "pathlib.py", line 1104, in home
  File "pathlib.py", line 267, in gethomedir
RuntimeError: Can't determine home directory

它来自脚本中matplotlib的导入。我不确定这是否是一个简单的 PATH 问题,或者是否还有其他一些微妙之处源于混合使用 MSYS 和 MinGW 来编译所有内容。

我创建了一个最小的工作示例,它从我的大型 GTK 应用程序中复制了问题。由于这需要一些 DLL 和 makefile 才能运行,而不是简单地在此处发布代码,我创建了一个带有 README 的 GitHub 存储库,其中介绍了需要做什么来显示问题以及我做了什么来解决问题。您可以使用以下方法克隆它:

git clone https://github.com/LeighKorbel/pyinstaller_msys2_issue.git

问题在于我不确定究竟是什么解决了这个问题,无论发生了什么,我都无法在我的实际 GTK 程序中重现相同的解决方案。自述文件中概述了我的问题,因此我将在此处发布以更加清楚:

(1) Download and install MSYS2 from "https://www.msys2.org/".

(2) This will install both MSYS2 and MinGW to the msys2 directory on your local disk. Go into the msys2 folder and
open a MinGW64 shell as administrator.    

(3) In the MinGW shell, type in the following commands to install the required packages using the Pacman package manager:

    pacman -Syu     (NOTE: You will have to perform this call and close the shell several times for it to complete!)
    pacman -S mingw-w64-x86_64-python-pip
    pacman -S mingw-w64-x86_64-python-matplotlib
    pacman -S mingw-w64-x86_64-gcc mingw-w64-x86_64-gtk3 mingw-w64-x86_64-python3 mingw-w64-x86_64-python3-gobject
    pacman -S git make nano pkg-config gcc

(4) Clone the GitHub repository with the example showing the error in question:

    mkdir testing
    cd testing
    git clone https://github.com/LeighKorbel/pyinstaller_msys2_issue.git
    cd pyinstaller_msys2_issue

(5) Download PyInstaller using Python's pip package manager:

    pip install pyinstaller

(6) Now we are ready to start compiling. The first thing we need to do is run PyInstaller on the python script. This
    script is just a simple matplotlib plot that is embedded within a GTK window. Run the following commands:

    cd gtk_simple_plot
    pyinstaller plot.py

(7) After PyInstaller finishes, an executable file will now be located in "/dist/plot". This can be run from the 
    command line by:

    ./dist/plot/plot.exe

(8) Now that we have verified that this works, we want to compile a very basic GTK application in C that only has a 
    button that will spawn the script using GLib's g_spawn() function. However, this needs to be done in a MSYS2 shell
    because MinGW does not have the system headers needed in it. So you need to open an MSYS2 shell now as an administrator.
    After this is done, we will need to set an environment variable for pkg_config, which is used by GTK. I would
    recommend adding the following line to the .bashrc file and then closing and reopening the MSYS2 shell, but it is
    not required (you will just need to remember to export again in the case that you close and reopen the shell). Once
    the MSYS2 shell is open type in:

    nano .bashrc

(9) Scroll to the bottom of the file and type in the following:

    export PKG_CONFIG_PATH="/mingw64/lib/pkgconfig:$PKG_CONFIG_PATH"

(10) Now exit and save by pressing "Ctrl-x", then "y", then "enter". Close and reopen the shell. Now enter the following commands:

    cd testing/call_gtk_plot_from_C
    make

(11) The GTK program will now be compiled and can be executed by running:

    ./launch.exe

(12) Press the button on the UI to launch the PyInstaller compiled matplotlib script. The following error should
     appear in the MSYS2 shell:

    Traceback (most recent call last):
      File "plotter.py", line 6, in <module>
      File "<frozen importlib._bootstrap>", line 991, in _find_and_load
      File "<frozen importlib._bootstrap>", line 975, in _find_and_load_unlocked
      File "<frozen importlib._bootstrap>", line 671, in _load_unlocked
      File "PyInstaller/loader/pyimod03_importers.py", line 531, in exec_module
      File "matplotlib/__init__.py", line 921, in <module>
      File "matplotlib/__init__.py", line 602, in matplotlib_fname
      File "matplotlib/__init__.py", line 599, in gen_candidates
      File "matplotlib/__init__.py", line 239, in wrapper
      File "matplotlib/__init__.py", line 502, in get_configdir
      File "matplotlib/__init__.py", line 444, in _get_xdg_config_dir
      File "pathlib.py", line 1104, in home
      File "pathlib.py", line 267, in gethomedir
    RuntimeError: Can't determine home directory

********************************************************************************************************************
QUESTION 1: WHAT IS THE CAUSE OF THIS ERROR? IS THIS FROM THE DUAL USE OF MinGW FOR PYINSTALLER AND MSYS2 FOR MAKE?
            IS IT A PATH ISSUE?
********************************************************************************************************************

(13) This error can be eliminated by performing the following: In the "call_gtk_plot_from_C" folder, edit both the 
     makefile and the launch.c code (in src/) to remove all instances of "../gtk_simple_plot/" and then copy and move 
     the plot.py script from the gtk_simple_plot directory into the call_gtk_plot_from_C directory. From a MinGW shell
     run make.

     NOW IT WILL MAKE EVERYTHING, EVEN THE C CODE.

(14) Run the program:

     ./launch.exe

     When you run this time it errors out with a gdk_pixbuf error. For some reason, in /dist/plot/lib/gdk-pixbuf there
     is no longer the folder "loaders" which does exist on the previous run of PyInstaller. Copy it from the dist/
     folder in gtk_simple_plot over to the dist/ folder in call_gtk_plot_from_C. Run the program again.

     NOW IT WORKS WITHOUT THE PREVIOUS HOME DIRECTORY ERROR.

********************************************************************************************************************
QUESTION 2: WHAT CAUSED IT TO WORK NOW? WAS IT THE FULL COMPILE IN MinGW? IS THERE A PATH THAT IS DIFFERENT NOW WITH
            THE PYTHON SCRIPT BEING MOVED INTO THE NEW DIRECTORY? WHY WAS THE PIXBUF MISSING?
********************************************************************************************************************

********************************************************************************************************************
QUESTION 3: WHY DOES THE C CODE COMPILE IN MinGW NOW? IT DOES NOT CONTAIN THE SYSTEM HEADERS REQUIRED FOR IT COMPILE?
********************************************************************************************************************

您可以看到,我概述的三个问题对我来说仍然很神秘。如果有人可以帮助我解决其中任何一个问题,将不胜感激。

标签: matplotlibgtkmingwpyinstallermsys2

解决方案


问题是在不同的环境中使用 gcc。这个问题有点复杂,因为我有一个用 C 编写的 GTK 程序(依赖于 POSIX 系统头文件,所以需要 MSYS2),但是这个程序正在使用 PyInstaller 生成一个冻结 Python 脚本的子进程(它也使用 GTK将 matplotlib 图嵌入 GTK 窗口)。PyGobject 库(包含 GLib 和 GTK)需要使用 MinGW shell 才能编译它。所以一方面我有一些需要 MSYS2 的东西,另一方面我有一些需要 MinGW 的东西。

所以问题1的答案是:

它是由同时使用 MSYS2 和 MinGW 引起的,但真正的原因是使用 MSYS2 gcc 编译 C 代码和 MinGW gcc 在 Python 脚本上运行 PyInstaller。这个问题确实是一个 PATH 问题。

这是因为 Python 脚本继承了 MinGW 的环境变量,而 C GTK 代码继承了 MSYS2 的环境变量。当 Python 脚本无法确定主目录时,这是因为它们根本不存在,因此当 C 代码中的 g_spawn() 发生时,python 脚本会出现 HOMEPATH 错误。

问题2的答案是:

它现在可以工作,因为一切都是在 MinGW 环境中编译的一个简单事实,所以现在 C 代码缺少它以前没有的环境变量。

问题 3 的答案是:

C 代码在 MinGW 中正常编译。这最终成为一个糟糕的最小示例,因为它不依赖于任何系统头文件。在我的实际代码中,情况并非如此。我必须确保一切都在 MinGW 中编译,这需要使用 MSYS2 gcc 进行编译,以便获得所需的系统头文件并能够使用 PyGobject 编译 python 脚本。为了让它工作,我需要使用 g_setenv() 和 g_get_home_dir() 在 C 代码中设置环境变量。这然后允许 g_spawn() 拥有它以前缺少的环境变量。


推荐阅读