renpy / pygame_sdl2

Reimplementation of portions of the pygame API using SDL2.
GNU Lesser General Public License v2.1
322 stars 63 forks source link

pygame_sdl2.font ImportError when running programs frozen with PyInstaller #94

Open MyreMylar opened 6 years ago

MyreMylar commented 6 years ago

I've spent some time poking at making a usable windows .exe file of my pygame_sdl2 project with PyInstaller. After a fair bit of fiddling around and learning the ropes I've found two issues so far:

  1. The json library, is not included automatically by PyInstaller. I assume this is related to the way it is being dynamically imported somewhere in pygame_sdl2. Luckily it has a fairly easy fix in that you can just 'import json' in your own code and it will be included in your frozen distribution. It would be nice to make pygame_sdl2 handle this itself though.

  2. pygame_sdl2.font fails to import at runtime after freezing. Googling around seems to suggest this can come from a missing .dll file, but I'm at a loss as to exactly what that could be as the error helpfully gives no specifics. I have the obvious SDL_ttf.dll and SDL2_ttf.dll in there. In case it helps here is a screenshot of all the dlls PyInstaller scoops up into it's distribution directory:

alt text

Of course it could be something else related to the font module and not dlls causing the import error.

However, if you remove all traces of pygame_sdl2 font from your application it appears to build and run fine. So we are so close to functional here!

renpytom commented 6 years ago

I'm not much of a windows expert, but I'd suggest using something like https://docs.microsoft.com/en-us/sysinternals/downloads/procmon to see what it's accessing when it crashes.

MyreMylar commented 6 years ago

I had a poke around with procmon but concluded after a lot of staring at lists of event that it probably wasn't a .dll issue. Further investigation confirms this.

Digging into it further with good old trial and error, taking imports into and out of my test programs I've narrowed the remaining issue (assuming you handle the minor json import problem) down to importing pygame_sdl2.font failing in a frozen program, if you also import pygame.transform - that's from the regular version of pygame. Which doesn't sound like too much of a bother, except it turns out that pyTMX (the best library for importing TMX files from the Tiled editor) always imports pygame.transform.

I'm also slightly concerned because I currently have no idea why this happens. I can get programs to run in frozen mode with pygame_sdl2.font and without pygame.transform, and visa versa. It could be some general issue with the font module conflicting with other libraries that may crop up elsewhere, or it could be just related to pygame.transform. I'd be curious to know if this happens when freezing on other platforms.

Here's the test program I'm using on python 3.6:

https://gist.github.com/MyreMylar/a0c945cc6782f06d138930c1395e154e

And here's the spec file for PyInstaller:

https://gist.github.com/MyreMylar/7a8a97e4d181fe914aaa44733fe0f127

I'll probably hammer at it a bit further, you never know when new inspiration will strike, but if anybody has any good ideas about this problem I'd love to hear them.

renpytom commented 6 years ago

You can try pygame_sdl2.import_as_pygame(), which will override the pygame imports with their pygame_sdl2 equivalents. I don't support mixing the two in the same process, and since I don't use it, I don't support PyInstaller myself.

MyreMylar commented 6 years ago

I had a go with pygame_sdl2.import_as_pygame() but it had no effect, probably because it works dynamically and I think the way the freeze libraries work (still getting up to speed :) ) is through static code analysis of your imports and then pulling in and loading any code and dlls they require.

My feeling, after a couple more tests, is that this might be a problem of too many dlls rather than too few. I suspect that some of the SDL1 era dlls that pygame uses are incompatible with pygame_sdl2's SDL2 era versions, and that somehow by loading up the SDL1 versions at all your program will crash.

Anyway, assuming my read on the situation is accurate that doesn't sound like a solveable problem for pygame_sdl2's code. Probably worth documenting at some point though that pygameand pygame_sdl2 are incompatible on some level and that users should be aware that they may run into difficulties combining the two.

Specific to my particular program (and for anyone else who has the exact same issue) I'm pursuing getting a pygame dependency removed from PyTMX here.

Skipping back to the top post again. I'll try and submit a pull request later (once I unwind some of my testing) for the json library import issue, which I think will just be adding import json to the def _optional_imports(): function in pygame_sdl2/__init__.py. If that sounds OK, it doesn't quite match everything else going on in there but it seems the most appropriate place. Unless adding import pygame_sdl2.render there also fixes that problem, in which case I'll just do that because it matches.

Apologies for all the blather, but hopefully it helps somebody else sometime in the future.

Remusforte commented 5 years ago

I ran into the same pygame_sdl2.font ImportError as described in the original post. Here is the specific error message from the EXE that PyInstaller created:

Import error: DLL load failed: The specified procedure could not be found.

So there is definitely an issue related to the .dll for managing pygame_sdl2.font

After lots of digging and head-banging, I managed to trace the root cause to this .dll file: libfreetype-6.dll

It turns out that when PyInstaller does its magic, it is grabbing that .dll file from pygame and sticking it into the EXE's root directory. And that is what is used when the EXE launches. Unfortunately, the libfreetype-6.dll packaged with pygame_sdl2 is not the same! PyInstaller grabs that .dll also, but sticks it in a pygame_sdl2 subfolder in the EXE... and as far as I can tell, that is never accessed! And the pygame version of this .dll is not compatible with pygame_sdl2.

Solution: After Pyinstaller works its magic, I simply copy the pygame_sdl2 version of this .dll file and replace the pygame version that is in the root directory of the EXE. Bingo! No more complaints about failure to load a pygame_sdl2.font .dll!

Note: I have tried unsuccessfully thus far to force PyInstaller to grab the correct pygame_sdl2 version of this .dll file at the outset by adjusting settings in the .spec file. So I am left with this manual fix every time I compile the EXE. If I find an automated solution via PyInstaller .spec file, I will post that later.