Bundling data files with PyInstaller (--onefile)

前端 未结 13 1582
清歌不尽
清歌不尽 2020-11-21 13:28

I\'m trying to build a one-file EXE with PyInstaller which is to include an image and an icon. I cannot for the life of me get it to work with --onefile.

<
13条回答
  •  花落未央
    2020-11-21 13:57

    Using the excellent answer from Max and This post about adding extra data files like images or sound & my own research/testing, I've figured out what I believe is the easiest way to add such files.

    If you would like to see a live example, my repository is here on GitHub.

    Note: this is for compiling using the --onefile or -F command with pyinstaller.

    My environment is as follows.

    • Python 3.3.7
    • Tkinter 8.6 (check version)
    • Pyinstaller 3.6

    Solving the problem in 2 steps

    To solve the issue we need to specifically tell Pyinstaller that we have extra files that need to be "bundled" with the application.

    We also need to be using a 'relative' path, so the application can run properly when it's running as a Python Script or a Frozen EXE.

    With that being said we need a function that allows us to have relative paths. Using the function that Max Posted we can easily solve the relative pathing.

    def img_resource_path(relative_path):
        """ Get absolute path to resource, works for dev and for PyInstaller """
        try:
            # PyInstaller creates a temp folder and stores path in _MEIPASS
            base_path = sys._MEIPASS
        except Exception:
            base_path = os.path.abspath(".")
    
        return os.path.join(base_path, relative_path)
    

    We would use the above function like this so the application icon shows up when the app is running as either a Script OR Frozen EXE.

    icon_path = img_resource_path("app/img/app_icon.ico")
    root.wm_iconbitmap(icon_path)
    
    

    The next step is that we need to instruct Pyinstaller on where to find the extra files when it's compiling so that when the application is run, they get created in the temp directory.

    We can solve this issue two ways as shown in the documentation, but I personally prefer managing my own .spec file so that's how we're going to do it.

    First, you must already have a .spec file. In my case, I was able to create what I needed by running pyinstaller with extra args, you can find extra args here. Because of this, my spec file may look a little different than yours but I'm posting all of it for reference after I explain the important bits.

    added_files is essentially a List containing Tuple's, in my case I'm only wanting to add a SINGLE image, but you can add multiple ico's, png's or jpg's using ('app/img/*.ico', 'app/img') You may also create another tuple like soadded_files = [ (), (), ()] to have multiple imports

    The first part of the tuple defines what file or what type of file's you would like to add as well as where to find them. Think of this as CTRL+C

    The second part of the tuple tells Pyinstaller, to make the path 'app/img/' and place the files in that directory RELATIVE to whatever temp directory gets created when you run the .exe. Think of this as CTRL+V

    Under a = Analysis([main..., I've set datas=added_files, originally it used to be datas=[] but we want out the list of imports to be, well, imported so we pass in our custom imports.

    You don't need to do this unless you want a specific icon for the EXE, at the bottom of the spec file I'm telling Pyinstaller to set my application icon for the exe with the option icon='app\\img\\app_icon.ico'.

    added_files = [
        ('app/img/app_icon.ico','app/img/')
    ]
    a = Analysis(['main.py'],
                 pathex=['D:\\Github Repos\\Processes-Killer\\Process Killer'],
                 binaries=[],
                 datas=added_files,
                 hiddenimports=[],
                 hookspath=[],
                 runtime_hooks=[],
                 excludes=[],
                 win_no_prefer_redirects=False,
                 win_private_assemblies=False,
                 cipher=block_cipher,
                 noarchive=False)
    pyz = PYZ(a.pure, a.zipped_data,
                 cipher=block_cipher)
    exe = EXE(pyz,
              a.scripts,
              a.binaries,
              a.zipfiles,
              a.datas,
              [],
              name='Process Killer',
              debug=False,
              bootloader_ignore_signals=False,
              strip=False,
              upx=True,
              upx_exclude=[],
              runtime_tmpdir=None,
              console=True , uac_admin=True, icon='app\\img\\app_icon.ico')
    

    Compiling to EXE

    I'm very lazy; I don't like typing things more than I have to. I've created a .bat file that I can just click. You don't have to do this, this code will run in a command prompt shell just fine without it.

    Since the .spec file contains all of our compiling settings & args (aka options) we just have to give that .spec file to Pyinstaller.

    pyinstaller.exe "Process Killer.spec"
    

提交回复
热议问题