When you execute PyInstaller, the first thing it does is build a .spec file. This file is stored in the --specpath directory, which defaults to the current directory. The spec file is actually executable Python code - PyInstaller builds applications by executing this file.

Consider this example command:

pyinstaller -F myscript.py

PyInstaller first creates a myscript.spec file that encodes most options passed to the pyinstaller command. The generated myscript.spec file contents:

# -*- mode: python ; coding: utf-8 -*-

a = Analysis(
    ['myscript.py'],
    pathex=[],
    binaries=[],
    datas=[],
    hiddenimports=[],
    hookspath=[],
    hooksconfig={},
    runtime_hooks=[],
    excludes=[],
    noarchive=False,
    optimize=0,
)
pyz = PYZ(a.pure)

exe = EXE(
    pyz,
    a.scripts,
    a.binaries,
    a.datas,
    [],
    name='myscript',
    debug=False,
    bootloader_ignore_signals=False,
    strip=False,
    upx=True,
    upx_exclude=[],
    runtime_tmpdir=None,
    console=True,
    disable_windowed_traceback=False,
    argv_emulation=False,
    target_arch=None,
    codesign_identity=None,
    entitlements_file=None,
)

Typically, you won't need to inspect or modify the spec file contents.

pyi-makespec Usage

You can use the pyi-makespec command to create spec files:

This command shares identical syntax with pyinstaller, but only generates spec files without building executables.

Reference:

pyi-makespec [options] name.py [other scripts ...]

After creating and customizing a spec file, build your application by passing it to pyinstaller:

pyinstaller [options] name.spec

Most command options are encoded in the spec file during creation. If specified during pyinstaller execution, they'll be ignored.

When building from spec files, only these options remain effective:

  • --upx-dir
  • --distpath
  • --workpath
  • --noconfirm
  • --clean
  • --log-level

SPEC File Deep Dive

The spec file primarily uses four classes: Analysis, PYZ, EXE, and COLLECT.

Analysis: Analyzes Python script dependencies and identifies files needing bundling.

a = Analysis(
    ['myscript.py'],        # Main script (equivalent to `pyinstaller myscript.py`)
    pathex=[],              # Additional module search paths (equivalent to `--paths=DIR`)
    binaries=[],            # Additional binaries (DLLs, .so files) - no CLI equivalent
    datas=[],               # Additional data files (equivalent to `--add-data`)
    hiddenimports=[],       # Implicit imports (equivalent to `--hidden-import`)
    hookspath=[],           # Custom hook paths (equivalent to `--additional-hooks-dir`)
    hooksconfig={},         # Hook behavior configuration - no CLI equivalent
    runtime_hooks=[],       # Pre-execution hook scripts (equivalent to `--runtime-hook`)
    excludes=[],            # Modules to exclude (equivalent to `--exclude-module`)
    noarchive=False,        # Disable .pyz packaging (equivalent to `--noarchive`)
    optimize=0,             # Python optimization level: 0=default, 1=`-O`, 2=`-OO` (equivalent to `--optimize`)
)

PYZ: Creates a zlib-based PYZ archive containing byte-compiled pure Python modules (packages .pyc files into .pyz).

Rarely needs manual modification. If noarchive=True, no .pyz file will be generated.

pyz = PYZ(a.pure)

EXE: Generates the final executable.

exe = EXE(
    pyz,                        # PYZ archive
    a.scripts,                  # Main script wrapper
    [],                         # Other zip files (usually empty)
    exclude_binaries=True,      # Equivalent to --onedir (defers binary packaging)
    name='myscript',            # Output executable name (equivalent to `--name`)
    debug=False,                # Enable debug info (equivalent to `--debug`)
    bootloader_ignore_signals=False, # Ignore signals (for containerized environments)
    strip=False,                # Reduce size via strip (equivalent to `--strip`)
    upx=True,                   # Enable UPX compression (automatically enabled via `--upx-dir`)
    console=True,               # Show console (equivalent to `--console` for CLI or `--windowed` for GUI)
    disable_windowed_traceback=False, # Disable GUI error popups (equivalent to `--disable-windowed-traceback`)
    argv_emulation=False,       # macOS only (equivalent to `--argv-emulation`)
    target_arch=None,           # Target architecture (e.g. 'x86_64', equivalent to `--target-arch`)
    codesign_identity=None,     # macOS code signing identity
    entitlements_file=None,     # macOS entitlements configuration
)

COLLECT: Used only in one-dir mode to package executables and dependencies into output directory.

coll = COLLECT(
    exe,
    a.binaries,
    a.datas,
    strip=False,           # Strip resource files (size reduction)
    upx=True,              # Apply UPX to resources
    upx_exclude=[],        # Files to exclude from compression (supports wildcards)
    name='myscript',       # Output directory name (equivalent to `--name`)
)