Solving Common PyInstaller Problems on macOS

How to fix file paths, reduce bundle sizes, and handle macOS-specific packaging issues
Heads up! You've already completed this tutorial.

If you've tried packaging a PyQt5 application on macOS with PyInstaller, there's a good chance you've hit at least one brick wall. Missing data files, mysteriously huge bundles, cryptic errors on Big Sur — these are all common stumbling blocks.

In this article, we'll walk through the most frequent PyInstaller problems on macOS and how to solve them. Whether you're dealing with files that can't be found at runtime, a 800MB app bundle for a simple script, or OpenGL framework errors after upgrading to Big Sur, you'll find practical solutions here.

File paths break after bundling

One of the first things people run into is that data files — CSVs, images, config files — that sat happily next to your script during development suddenly can't be found once the app is bundled.

This happens because PyInstaller changes how your application runs. When you bundle your app, especially as a one-file executable, the files get extracted to a temporary directory at runtime. The working directory is no longer the folder your script lives in, so typical approaches like using __file__ or os.getcwd() to locate nearby files won't point where you expect.

How PyInstaller handles runtime paths

PyInstaller sets a special attribute sys._MEIPASS when your app is running from a bundle. This points to the folder where your bundled data files are extracted. When running unbundled (during development), this attribute doesn't exist.

You can write a small helper function that works in both situations:

python
import sys
import os


def resource_path(relative_path):
    """Get the absolute path to a resource, works for development and PyInstaller bundles."""
    if hasattr(sys, '_MEIPASS'):
        # Running as a PyInstaller bundle
        base_path = sys._MEIPASS
    else:
        # Running in normal development mode
        base_path = os.path.abspath(".")

    return os.path.join(base_path, relative_path)

Then, anywhere you need to load a file:

python
import csv

csv_file = resource_path("mydata.csv")

with open(csv_file, "r") as f:
    reader = csv.reader(f)
    for row in reader:
        print(row)

This approach works identically whether you're running your script directly with Python or running the bundled application.

Making sure your data files are included

You also need to tell PyInstaller to actually include your data files. In your .spec file, add them to the datas list:

Bring Your PyQt/PySide Application to Market — Specialized launch support for scientific and engineering software built using Python & Qt.

Find out More

python
a = Analysis(
    ['main.py'],
    datas=[
        ('mydata.csv', '.'),
        ('other_file.csv', '.'),
    ],
    # ... rest of your config
)

The format is (source_path, destination_folder). Using '.' places the file at the root of the bundle.

If you're running PyInstaller from the command line rather than using a spec file, you can use the --add-data flag:

sh
pyinstaller --add-data "mydata.csv:." main.py

Note the colon : separator on macOS and Linux (Windows uses a semicolon ;).

An alternative: Qt Resource System

You might have seen recommendations to use the Qt Resource System (qrc files) to embed data directly into your application. This sidesteps the path problem entirely because the resources are compiled into your Python code — there are no external files to locate at runtime.

This is a solid approach, especially for things like icons, images, and other static assets. But it's not a requirement. The resource_path function above works perfectly well for CSV files and other data that you'd rather keep as separate files.

Reducing bloated bundle sizes

An 800MB app bundle for a straightforward PyQt5 application is frustrating, but it's a common experience. The root cause is how Python's import system works.

Python imports are dynamic. If your code imports PyQt5, then any submodule of PyQt5 — QtNetwork, QtWebSockets, QtWebEngine, and so on — could potentially be accessed at runtime. PyInstaller can't always tell which submodules you'll actually use, so it takes the safe approach and includes everything it finds.

The same thing happens with other large packages. If you have Jupyter, IPython, or NumPy installed in your environment, PyInstaller may pull them in through indirect dependencies.

Exclude modules you don't need

PyInstaller's --exclude-module flag lets you explicitly drop packages from the bundle:

sh
pyinstaller --exclude-module QtWebEngine \
            --exclude-module QtNetwork \
            --exclude-module QtWebSockets \
            --exclude-module IPython \
            --exclude-module jupyter \
            --exclude-module notebook \
            main.py

Or in your .spec file:

python
a = Analysis(
    ['main.py'],
    excludes=[
        'QtWebEngine',
        'QtNetwork',
        'QtWebSockets',
        'IPython',
        'jupyter',
        'notebook',
    ],
    # ...
)

After excluding unnecessary modules, rebuild and check the size. You should see a significant reduction.

Use a clean virtual environment

The single most effective way to keep bundle sizes down is to package from a clean virtual environment that only contains the packages your application actually needs. If you're using Anaconda with a large environment that includes Jupyter, IPython, and dozens of scientific packages, PyInstaller will try to pull many of them in.

Create a fresh virtual environment and install only what your app requires:

sh
python3 -m venv packaging_env
source packaging_env/bin/activate
pip install pyqt5 pyinstaller
# Install any other packages your app actually imports
pip install requests  # for example

Then run PyInstaller from within this clean environment. You'll often see bundle sizes drop dramatically — from 800MB to under 200MB is common.

Check what's being included

If your bundle is still larger than expected, you can investigate. PyInstaller generates a build/ directory with a warn-*.txt file and other logs that show which modules were detected and included. Look through these for surprises.

You can also inspect the .app bundle directly. On macOS, a .app is just a folder:

sh
# Right-click the .app in Finder and choose "Show Package Contents"
# Or from the terminal:
ls -la dist/MyApp.app/Contents/MacOS/

This lets you see exactly what's in there and identify the large items.

macOS Big Sur framework errors

After upgrading to macOS Big Sur (11.0), many users started seeing errors like this when running PyInstaller:

python
ERROR: Can not find path /System/Library/Frameworks/OpenGL.framework/Versions/A/OpenGL

This happens because Apple fundamentally changed how system libraries work in Big Sur. System frameworks and libraries were moved into a shared cache and are no longer present as individual files on disk. PyInstaller (and the tools it depends on) expected to find these files at their traditional paths.

Update PyInstaller

The most straightforward fix is to update PyInstaller to a version that understands Big Sur's changes:

sh
pip install --upgrade pyinstaller

Versions from 4.1 onward include fixes for Big Sur compatibility. If you're stuck on an older version for some reason, upgrading should be your first step.

Avoid Anaconda for packaging

Several users have reported that Anaconda environments cause additional problems with PyInstaller on macOS, particularly on Big Sur. The Anaconda Python distribution includes its own copies of various libraries, and these can conflict with what PyInstaller expects to find on the system.

If you're hitting persistent issues, try using the standard Python installation from python.org instead:

sh
# Install Python from python.org, then:
python3 -m venv myenv
source myenv/bin/activate
pip install pyqt5 pyinstaller
pyinstaller main.py

This combination — standard CPython from python.org, a clean virtual environment, and the latest PyInstaller — gives you the best chance of a smooth packaging experience on macOS.

Alternative packaging tools for macOS

PyInstaller is the most commonly used option, but it's not the only one. Two other tools are worth knowing about:

py2app is a macOS-specific tool that creates .app bundles. It uses a similar freezing approach to PyInstaller, so bundle sizes will be comparable, but some users find it handles certain macOS-specific edge cases better.

sh
pip install py2app

cx_Freeze is a cross-platform option that works on macOS, Windows, and Linux. Again, it uses the same general approach of bundling Python and your dependencies together.

sh
pip install cx_freeze

All three tools produce similar results in terms of bundle size because the fundamental approach is the same: they bundle a Python interpreter along with your code and its dependencies. The differences are mainly in configuration, compatibility with specific packages, and how they handle platform-specific details.

A complete packaging workflow for macOS

Putting it all together, here's a reliable workflow for packaging a PyQt5 app on macOS:

sh
# Create a clean virtual environment
python3 -m venv build_env
source build_env/bin/activate

# Install only what you need
pip install pyqt5 pyinstaller

# Install your app's other dependencies
pip install -r requirements.txt

# Build the app bundle
pyinstaller --windowed \
            --name "MyApp" \
            --add-data "mydata.csv:." \
            --exclude-module QtWebEngine \
            --exclude-module QtWebEngineCore \
            --exclude-module QtWebEngineWidgets \
            main.py

The --windowed flag (or -w) tells PyInstaller to create a proper macOS .app bundle without a console window, which is what you want for a GUI application.

After the build completes, you'll find your app in the dist/ folder. Test it thoroughly — open it from Finder, try all the features that load external files, and verify everything works as expected.

If you need to distribute the app to other users, you can compress the .app bundle into a .zip or create a .dmg disk image. For the .dmg approach, tools like create-dmg can automate the process and give you a professional-looking installer.

Packaging Python applications on macOS takes some patience and troubleshooting, but with a clean environment, the right PyInstaller settings, and awareness of the common pitfalls, you can produce a working app bundle that your users can run without needing Python installed on their machines.

PyQt6 Crash Course by Martin Fitzpatrick — The important parts of PyQt6 in bite-size chunks

See the course

Well done, you've finished this tutorial! Mark As Complete
[[ user.completed.length ]] completed [[ user.streak+1 ]] day streak
Martin Fitzpatrick

Solving Common PyInstaller Problems on macOS was written by Martin Fitzpatrick.

Martin Fitzpatrick is the creator of Python GUIs, and has been developing Python/Qt applications for the past 12+ years. He has written a number of popular Python books and provides Python software development & consulting for teams and startups.