PyInstaller but keeping .py files upgradeable - python

I've managed to package my PyQt4 app as a "standalone" application on windows, it works.
However this application can upgrade itself, which is done by replacing the actual code written by me (.py files) with new versions, downloaded via the internet.
How can I tell PyInstaller do its job (putting together the DLLs, generating the launcher with the shiny icon, etc), BUT let the .py files untouched?
I need those files directly on disk, in order for the auto-update to work.

You can change the spec file to specifically not include files by name (when building lists), then make sure these files are included - I'd have to check whether there's an option to include but not compile.
I've not tried this myself (I use pyInstaller at work but don't have it set up on my home PC) but this is the sort of thing I think should be ok:
a = Analysis(['main.py'])
excluded = ['myfile0.py', 'myfile1.py', 'myfile2.py']
a.scripts = [script from script in a.scripts if script not in excluded]
pyz = PYZ(a.pure)
exe = EXE(a.scripts, pyz, name="main.exe", exclude_binaries=1)
dist = COLLECT(exe, a.binaries, excluded, name="dist")

Actually it's more like this :
a = Analysis(['main.py'])
excluded = ['pathto\\myfile0.py', 'pathto\\myfile1.py', 'pathto\\myfile2.py']
a.scripts = [script from script in a.scripts if script[1] not in excluded]
pyz = PYZ(a.pure)
excluded_files_collect = [(f.split('\\')[-1],f,'DATA') for f in excluded]
exe = EXE(a.scripts, pyz, name="main.exe", exclude_binaries=1)
dist = COLLECT(exe, a.binaries, excluded_files_collect , name="dist")
As script is actually a tuple with the form :
('myfile0.py', 'pathto\\myfile0.py', 'PYSOURCE')
You may also have to prevent files from being included in PYZ, refer to the pyz toc to see if they get included, I managed to exlude them using excludes=[myfile0] in Analysis().

I think the embedded interpreter in the executable will still search for .py files in the same directory and/or PYTHONPATH, py2exe uses a zip file for native python components, iirc pyinstaller embeds all of them in the executable, maybe there is an option to keep a zip like in py2exe (or not add them in the spec), then try to run the application without the files and monitor file accesses with procmon.

pyinstaller provides the --exclude option for your use case , and it is also possible to set the module or package you want to ignore using the excludes parameter of Analysis() in the .spec file .

Related

Can PyInstaller pack shared object files into executable?

I've written a Python app which uses the tkinter module (among others) on Linux.
Python(3.10) with tkinter support was compiled by myself in a custom location (~/local), as well as the non-python dependencies like tk/tcl, libfreetype2, libpng, etc.
I've then packaged the script with PyInstaller using the --one-file option.
The resulting executable works if I execute it myself.
But copying it to another location and executing it as a different user leads to an
ImportError: /home/*****/local/lib/libtcl8.6.so: cannot open shared object file: Permission denied, because of course that folder is not readable by that user.
I've tried bundling the .so file with both the --add-data and --add-binary option of PyInstaller, but none of it works. Even if I copy the files manually, it's still looking in the custom path.
Is there a way to specify to PyInstaller to package the needed shared object files into the executable or at least change any absolute path into a relative one, so I can bundle the files manually?
You should have a .spec file created first time you have created an executable, Explicitly add all the files which need to be shipped with executable in the spec file data .
https://pyinstaller.org/en/stable/spec-files.html
For example , add datafiles and add it to datas in spec file :
data_files = [(os.path.join(some_path,some_file), '.'), ]
block_cipher = None
a = Analysis(['minimal.py'],
pathex=['/Developer/PItests/minimal'],
binaries=None,
datas=data_files,
hiddenimports=[],
hookspath=None,
runtime_hooks=None,
excludes=None,
cipher=block_cipher)
pyz = PYZ(a.pure, a.zipped_data,
cipher=block_cipher)
exe = EXE(pyz,... )
coll = COLLECT(...)
Now to generate pyinstaller executable use this spec file .
pyinstaller somename.spec
Do note that in your script dont directly load the files shipped in executable using explict paths instead load in this way , since pyinstaller during runtime in other environments dont pick the embedded files through explict paths but store in its file system so this take care of it :
folder_path = getattr(sys, '_MEIPASS', some_path)
file_path = os.path.join(folder_path,some_file)

Pygame.image.load() not working with PyInstaller

img_l = pygame.image.load("img.png")
screen.blit(img_l)
With Python Interpreter it works fine, image loads and main.py runs without problems, but when I make it into one file .exe with Pyinstaller, .exe crashes.
I've tried few .spec modifications, but few I've managed to find don't seem to help.
Any ideas sincerely appreciated.
EDIT: got it working with:
img_l = pygame.image.load(os.path.join('img.png'))
screen.blit(img_l, (0, 0))
Now it works as it should after going through PyInstaller :)
For anyone else who is having the same problem and has tried #Esa's answer, you may notice that it sometimes does not work when run outside the directory. This is caused by Pyinstaller still trying to find the relative path and not using the included files instead. This can be fixed by finding the correct path inside your code before loading files:
if getattr(sys, 'frozen', False):
Path = sys._MEIPASS #This is for when the program is frozen
else:
Path = os.path.dirname(__file__) #This is when the program normally runs
This finds the actual path of your file and must be done for every file, e.g:
pygame.image.load(os.path.join(Path, 'Path\\to_file\\from_root'))
sys._MEIPASS is the key as it will find the path when the program is frozen as when a program is frozen, it's files are stored somewhere else.
When you generate the .exe file a .spec file will also be created with the root directory. In there you will need to edit this as follows:
You should see a file structure similar to the one below
Notice how datas is equal to None. You will have to edit this.
This is the .spec file in the root directory currently:
# -*- mode: python -*-
block_cipher = None
#you will have to add things here later
a = Analysis(['file.py'],
pathex=['C:\\path\\to\\root\\folder'],
binaries=None,
datas=None,
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,
exclude_binaries=True,
name='game_file',
debug=False,
strip=False,
upx=True,
console=True , icon='icon_file.ico')
Now what we do is we add all our files: Place this added_files below block_cipher but above Analysis(), e.g:
added_files = [
('file1.png', '.') #The '.' represents root folder
('file2.png', '\\folder') #loads file2.png from root\folder
]
Now inside of Analysis() we change the None after datas to added_files. You will also notice that there are the main different options for your final file. You can edit these here if you want but you cannot change the "onefile" option, this is made when the .spec file is created.
Finally to make this new exe file, go to your root folder in cmd and type this:
pyinstaller "PY_FILE_NAME.spec"
The final exe, whether in a folder or not should be in root\dist\Executable_name.exe or root\dist\Executable_name\Executable_name.exe
I ran into this same problem. For some reason the working directory is not set properly for the --onefile packaged executable. I managed to resolve it by using the following piece of code at the beginning of my program.
This is similar to the above proposed solutions but I find it slightly more elegant :)
It basically forces the working directory to the temporary extracted directory in case we are running as a single executable.
import sys
import os
if getattr(sys, 'frozen', False):
os.chdir(sys._MEIPASS)
got it working with:
img_l = pygame.image.load(os.path.join('img.png'))
screen.blit(img_l, (0, 0))
Now it works as it should after going through PyInstaller :)
Sorry, I'm very very new to programming.
I'm getting a similar (the same?) error when I try to run my .exe
It says:
pygame.error: Couldn't open walkr1.png
I tried the fix that you used, but then got the error:
pygame.error: Can't seek in this data source
The image files are in a folder in the folder with the main .py file. I've tried moving the files to the same folder but that didn't work. I've also tried adding the data files to the .spec file, but that doesn't seem to work either...
Was wondering if you could help?
Sorry, I know these are probably all very dumb questions.

pyinstaller doesn't bundle pyd and dll files with --onefile

I'm trying to bundle a cefpython1 application into a single exe with pyinstaller.
I have a working spec file which creates a distrubiton for the cefpython1 example, cefsimple:
# -*- mode: python -*-
def get_cefpython_path():
import cefpython1 as cefpython
path = os.path.dirname(cefpython.__file__)
return "%s%s" % (path, os.sep)
cefp = get_cefpython_path()
a = Analysis(['cefsimple.py'],
hiddenimports=["json.decoder", "json.scanner", "json.encoder"])
pyz = PYZ(a.pure)
exe = EXE(pyz,
a.scripts,
exclude_binaries=True,
name='cefsimple.exe',
debug=False,
strip=None,
upx=False,
console=False )
coll = COLLECT(exe,
a.binaries + [('icudt.dll', '%s/icudt.dll' % cefp, 'BINARY')],
a.zipfiles,
a.datas + [('locales/en-US.pak', '%s/locales/en-US.pak' % cefp, 'DATA'), ('cefsimple.html', 'cefsimple.html', 'DATA'), ('icon.ico', 'icon.ico', 'DATA')],
strip=None,
upx=False,
name='cefsimple')
The project files can be found on my Google Drive. Don't care about setup.py, it contains a py2exe build which i was playing with next to pyinstaller. You need Python 2.7, Win32gui, cefpython1 and of course pyinstaller packages to run this, I've tested this with the Win32 version only! I've even tried installing the development pyinstaller if it makes any change.
If i try to execute pyinstaller with the --onefile attribute seems like nothing changes, pyinstaller just creates the distribution directory under dist. The command I'm using is: pyinstaller --onefile cefsimple.spec
Tested --onefile with a simple Hello World python file and it does work by that. What's causing pyinstaller to not creating a single exe? The build log doesn't show anything interesting, but there are some things i don't understand in the warning file. For eg. it says there's no module named cefpython1.cefpython but the correct pyd is copied to the dist directory and the application is working anyway.
Here is the list of files created under dist/: cefsimple.lst Maybe this helps finding the problem.
The command I'm using is: pyinstaller --onefile cefsimple.spec
Tested --onefile with a simple Hello World python file and it does
work by that. What's causing pyinstaller to not creating a single exe?
The option --onefile is ignored when you type pyinstaller --onefile cefsimple.spec because the .spec already defines if you will get a directory or a standalone file. A .spec file with a COLLECT function will make a whole dist directory.
I would suggest remaking a new .spec file by typing pyi-makespec --onefile cefsimple.py and adding back your various modifications (data, binaries, hiddenimports...), then trying pyinstaller cefsimple.spec without option. That works for me with pyinstaller 3.3.1.

How to compile python script to binary executable

I need to convert a Python script to a Windows executable.
I have Python 2.6 installed to python26.
I have created one script and kept it in C:\pythonscript. Inside this folder there are two files
Setup.py and oldlogs.py (this file need coversion)
setup.py code is
from distutils.core import setup
import py2exe
setup(console=['oldlogs.py'])
How can I convert oldlogs.py to an exe file?
Or use PyInstaller as an alternative to py2exe. Here is a good starting point. PyInstaller also lets you create executables for linux and mac...
Here is how one could fairly easily use PyInstaller to solve the issue at hand:
pyinstaller oldlogs.py
From the tool's documentation:
PyInstaller analyzes myscript.py and:
Writes myscript.spec in the same folder as the script.
Creates a folder build in the same folder as the script if it does not exist.
Writes some log files and working files in the build folder.
Creates a folder dist in the same folder as the script if it does not exist.
Writes the myscript executable folder in the dist folder.
In the dist folder you find the bundled app you distribute to your users.
I recommend PyInstaller, a simple python script can be converted to an exe with the following commands:
utils/Makespec.py [--onefile] oldlogs.py
which creates a yourprogram.spec file which is a configuration for building the final exe. Next command builds the exe from the configuration file:
utils/Build.py oldlogs.spec
More can be found here
Since other SO answers link to this question it's worth noting that there is another option now in PyOxidizer.
It's a rust utility which works in some of the same ways as pyinstaller, however has some additional features detailed here, to summarize the key ones:
Single binary of all packages by default with the ability to do a zero-copy load of modules into memory, vs pyinstaller extracting them to a temporary directory when using onefile mode
Ability to produce a static linked binary
(One other advantage of pyoxidizer is that it does not seem to suffer from the GLIBC_X.XX not found problem that can crop up with pyinstaller if you've created your binary on a system that has a glibc version newer than the target system).
Overall pyinstaller is much simpler to use than PyOxidizer, which often requires some complexity in the configuration file, and it's less Pythony since it's written in Rust and uses a configuration file format not very familiar in the Python world, but PyOxidizer does some more advanced stuff, especially if you are looking to produce single binaries (which is not pyinstaller's default).
# -*- mode: python -*-
block_cipher = None
a = Analysis(['SCRIPT.py'],
pathex=[
'folder path',
'C:\\Windows\\WinSxS\\x86_microsoft-windows-m..namespace-downlevel_31bf3856ad364e35_10.0.17134.1_none_50c6cb8431e7428f',
'C:\\Windows\\WinSxS\\x86_microsoft-windows-m..namespace-downlevel_31bf3856ad364e35_10.0.17134.1_none_c4f50889467f081d'
],
binaries=[(''C:\\Users\\chromedriver.exe'')],
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='NAME OF YOUR EXE',
debug=False,
strip=False,
upx=True,
runtime_tmpdir=None,
console=True )

How to compile all resources into one executable file?

I've wrote GTK application with python.
All graphical user interface is in glade file, and there are some images used. I wish to compile my application into EXEcutable file. For that I'm using PyInstaller compiler and UPX packer.
I've done as manual says:
python Configure.py
python Makespec.py --onefile --windowed --upx /path/to/yourscript.py
python Build.py /path/to/yourscript.spec
PyInstaller works perfectly and create one exe file. But to make my application work correctly i have to copy my glade and image files into exe's folder.
Is there any way to compile those files into executable?
I've edited my spec file in various ways but i can not achieve what i want. Spec file below only copies file to directory, but does not compile into executable file
# -*- mode: python -*-
a = Analysis([os.path.join(HOMEPATH,'support\\_mountzlib.py'), os.path.join(HOMEPATH,'support\\useUnicode.py'), 'r:\\connection\\main.py'],
pathex=['C:\\Documents and Settings\\Lixas\\Desktop\\pyinstaller-1.5-rc1'])
pyz = PYZ(a.pure)
exe = EXE( pyz,
a.scripts,
a.binaries,
a.zipfiles,
a.datas,
name=os.path.join('dist', 'NetworkChecker.exe'),
debug=False,
strip=False,
upx=True,
console=False,
icon='r:\\connection\\ikona.ico' )
coll = COLLECT(
exe,
[('gui.glade', 'r:\\connection\\gui.glade', 'DATA')],
[('question16.png', 'r:\\connection\\question16.png', 'DATA')],
# a.binaries,
# strip=False,
upx=True,
name='distFinal')
I wish to have only one executable file with everything included into
PyInstaller does allow you to bundle all your resources into the exe, without the trickyness of turning your data files into .py files -- your COLLECT object seems to be correct, the tricky step is accessing these files at runtime. PyInstaller will unpack them into a temporary directory and tell you where they are with the _MEIPASS2 variable. To get the file paths in both development and packed mode, I use this:
def resource_path(relative):
return os.path.join(
os.environ.get(
"_MEIPASS2",
os.path.abspath(".")
),
relative
)
# in development
>>> resource_path("gui.glade")
"/home/shish/src/my_app/gui.glade"
# in deployment
>>> resource_path("gui.glade")
"/tmp/_MEI34121/gui.glade"
With a few changes, you can incorporate everything into your source code and thus into your executable file.
If you run gdk-pixbuf-csource on your image files, you can convert them into strings, which you can then load using gtk.gdk.pixbuf_new_from_inline().
You can also include your Glade file as a string in the program and then load it using gtk.Builder.add_from_string().
Is there any way to compile those files into executable?
Strictly speaking: no, because you compile source code, while the glade file is XML and the images are binary data. What you would normally do is to create an installer (i.e. a self-unpacking archive that will place different files in the correct directories when the installer is ran).
EDIT: If your concern is simply to have a one-file executable (so it's not about "compiling" but really about the number of files that are permanently written on the filesystem) you could try to use this script based on py2exe. What it does, is to create temporary files each time the program is ran, removing them when execution is completed.
EDIT2: Apparently what you are asking for is also possible under PyInstaller. From the documentation:
By default, pyinstaller.py creates a distribution directory containing the main executable and the dynamic libraries. The option --onefile specifies that you want PyInstaller to build a single file with everything inside.

Categories

Resources