Skip to main content

Coil

Python License Platform Version

A Python-to-executable compiler that just works.


Why Coil?

Existing tools for turning Python projects into executables — PyInstaller, cx_Freeze, Nuitka, py2exe — all share the same problem: they're complicated. Hidden imports, missing DLLs, spec files, hook scripts, cryptic errors. You spend more time fighting the tool than building your app.

Coil takes a different approach: directory in, executable out. Point it at your project folder, and it handles the rest. No spec files. No hook scripts. No per-file configuration.

  • Auto-detects entry points
  • Auto-detects dependencies via importlib.metadata — handles distribution-name mismatches (Pillow→PIL, windows-toasts→windows_toasts) and multi-module packages (pywin32) for free
  • Bundles an embedded Python runtime — no Python installation needed on the target machine
  • Produces a single portable .exe or a clean bundled directory
  • Per-entry VERSIONINFO stamping — Task Manager shows your app name, not "Python"
  • Per-entry PE subsystem control — pick console or GUI per executable in coil.toml
  • Processes nested .pth files and registers DLL search directories at runtime, so packages like pywin32 work with no shim
  • Built-in decompiler to recover your own source if you need it

Quick Start

pip install coil-compiler
coil init ./myproject        # Generate coil.toml config
coil build ./myproject       # Build your executable

Or, if coil isn't on your PATH:

python -m coil init ./myproject
python -m coil build ./myproject

That's it. Your executable is in ./dist/.

Installation

pip install coil-compiler

Requirements:

  • Python 3.9 or later
  • Windows (macOS and Linux support planned)
  • pip (for dependency installation during builds)

Windows note: If coil isn't recognized after install, your pip Scripts directory may not be on PATH. You can either:

  • Use python -m coil instead (works the same way — all examples below apply)
  • Or add Python's Scripts directory to your PATH (python -m site --user-site will show you where it is)

Usage

Basic Build

# Auto-detect entry point, build portable exe
coil build ./myproject

Specify Entry Point

coil build ./myproject --entry app.py

Portable Mode (Default)

Single standalone .exe. Copy it anywhere and run it. No installation needed.

coil build ./myproject --mode portable

Bundled Mode

Directory containing compiled application files. Multiple scripts become multiple compiled files.

coil build ./myproject --mode bundled

Runtime behavior

Bundled executables are native application launchers. MyApp.exe --flag "a path" reaches Python as [absolute_exe_path, "--flag", "a path"]; Python's command-line options and environment configuration do not consume application arguments. sys.executable identifies the application, sys.frozen is true, and Windows multiprocessing spawn is supported. Portable applications expose the outer exe as their executable identity. Renaming an exe preserves its bound entry point.

Application code and dependencies use target-version bytecode. Project assets follow .coilignore recursively and are available at bundle root and beside compiled modules for package resource access. The working directory still changes to the bundle/cache root; resolve caller-relative inputs deliberately. A GUI app without stderr still needs application-level error reporting. sys.executable relaunches the app; it is not a Python command for -c or -m subprocesses.

The full audit report lists reproduced defects, fixes, validation, and remaining choices, including stdlib stripping and dynamic-import limits.

GUI Application

Coil auto-detects GUI frameworks. If your project imports tkinter, PyQt5, PyQt6, PySide2, PySide6, wx, kivy, pygame, pyglet, dearpygui, customtkinter, flet, pystray, infi.systray, plyer, or windows_toasts, the console window is hidden automatically.

# No --gui needed — Coil detects tkinter/PyQt5/etc. and hides the console
coil build ./myproject

# Force console window even with GUI imports
coil build ./myproject --console

# Explicitly set GUI mode (redundant if auto-detected, but works)
coil build ./myproject --gui

Specify Python Version

coil build ./myproject --python 3.12

Dependency Control

# Exclude packages
coil build ./myproject --exclude numpy,pandas

# Force-include packages
coil build ./myproject --include extra-lib

# Use a specific requirements file
coil build ./myproject --requirements ./reqs.txt

Bytecode Optimization

Control the Python bytecode optimization level:

coil build ./myproject --optimize 0    # No optimization (default for dev)
coil build ./myproject --optimize 1    # Strip assert statements (default)
coil build ./myproject --optimize 2    # Strip asserts + docstrings (default for --secure)

Multiple Entry Points

Each entry point produces its own executable. In bundled mode, all entry points share the same runtime:

coil build ./myproject --entry cli.py --entry gui.py --mode bundled

GUI detection is per-entry — if gui.py imports PyQt5 but cli.py doesn't, only gui.exe will have its console hidden.

Secure Build

Heavy obfuscation. Cannot be reversed by coil decompile:

coil build ./myproject --secure

Decompile

Recover source from a default (non-secure) Coil build:

coil decompile ./dist/MyApp.exe --output ./recovered

Secure builds cannot be recovered by Coil; Python bytecode remains inspectable.

Clean Build

Build in an isolated environment with only declared dependencies. Caches dependencies for the selected target runtime. Pin dependencies when repeatable version selection matters:

coil build ./myproject --clean

The clean environment is cached — subsequent builds reuse it if dependencies haven't changed.

Project Setup

Generate a coil.toml config file for your project:

coil init ./myproject

This asks a few questions (entry point, console/GUI, icon) and writes a coil.toml with sensible defaults. After that, coil build ./myproject uses the config automatically — no flags needed.

Build Profiles

Define named profiles in coil.toml for different build scenarios:

[profile.dev]
mode = "bundled"
secure = false
verbose = true

[profile.release]
mode = "portable"
secure = true

Switch between them with --profile:

coil build ./myproject --profile dev
coil build ./myproject --profile release

CLI flags always override profile settings.

Excluding Files (.coilignore)

Coil ships with sensible default excludes for things that don't belong in a bundle: __pycache__/, .git/, .github/, .vscode/, .idea/, .cursor/, *.egg-info/, build/, dist/, Output/, tests/, test/, testing/, memory/, coil.toml, pyproject.toml, setup.py, requirements*.txt, *.iss, build.bat, Makefile, .gitignore, README*, LICENSE*, CHANGELOG*, *.log, prompt.md, and helper scripts not declared as entry points.

Create a .coilignore file in your project root to extend those defaults. Works like .gitignore — one glob pattern per line, # for comments, and !pattern to negate (un-exclude) a default:

# Project-specific excludes
*.zip
test_data/
docs/

# Un-exclude a file the defaults would have caught
!README.md

.coilignore patterns are unioned with the built-in defaults; negation patterns let you override defaults when they're wrong for your project.

Pre-Build Diagnostics

Check for problems before building:

coil doctor ./myproject

Checks Python version, runtime availability, write permissions, config validity, and known package issues.

Build Preview

See what Coil will include in a build without building:

coil inspect ./myproject

Shows entry point, dependencies (stdlib vs third-party), estimated output size, and config.

Dry Run

See what would be built without building:

coil build ./myproject --dry-run --verbose

Full Example

coil build ./myproject \
  --entry main.py \
  --mode portable \
  --os windows \
  --python 3.12 \
  --gui \
  --secure \
  --exclude numpy,pandas \
  --output ./dist \
  --name MyApp \
  --icon ./assets/icon.ico

How It Works

  1. Dependency Resolution — Coil scans every .py file with Python's ast module to find imports, then maps each top-level module to its PyPI distribution via importlib.metadata.packages_distributions(). That mapping correctly handles distribution-name mismatches (Pillow → PIL), hyphen/underscore conventions (windows-toasts → windows_toasts), and multi-module packages (pywin32 ships win32api, win32com, pywintypes, etc.). When auto = true, the import-detected set is unioned with [project].dependencies and explicit include = [...], preserving version pins. A requirements file is an exclusive source plus explicit includes.

  2. Runtime Bundling — Downloads the official Windows embeddable Python distribution matching your target version. No C compiler needed.

  3. Compilation — All .py files are compiled to .pyc bytecode using the target Python version. If your build machine runs a different Python version than your target, Coil delegates compilation to the embedded runtime so .pyc magic numbers always match. Project/dependency source is compiled; small Coil runtime support scripts remain as .py files.

  4. Packaging — In portable mode, everything is packed into a single .exe file. In bundled mode, a clean directory with the runtime, compiled code, and dependencies. Each exe gets proper PE version info (product name, file description) so Windows shows the correct app name in task manager and file properties.

Portable Mode Details

The portable .exe is a single file you can copy anywhere and run. Here's what happens under the hood:

  • You distribute one file. The .exe contains a lightweight native launcher (about 165 KB) with the full application payload appended.
  • First launch extracts the runtime to a local cache (%LOCALAPPDATA%\coil\<app>\<build_hash>\). This is a one-time operation.
  • Subsequent launches validate the cache and repair missing or damaged runtime files when needed, preserving mutable data assets.
  • Each build gets a unique hash. Rebuilding your app creates a new cache entry. Old inactive cache entries are cleaned up, retaining three generations while protecting running builds. Legacy cache directories without leases remain for explicit cleanup.
  • Cache safety: Extraction uses file locking to prevent corruption if multiple instances launch simultaneously. A marker file ensures only fully-extracted caches are used — if extraction is interrupted, it will restart cleanly.

To manage the cache manually:

coil cache info     # Show cache location and size
coil cache clear    # Delete all cached runtimes

Tip: If you have a requirements.txt or pyproject.toml, Coil uses that instead of scanning imports. This is faster and more reliable.

Configuration (coil.toml)

Run coil init to generate a config file, or create one manually:

[project]
entry = "main.py"
name = "MyApp"
version = "1.0.0"   # Optional. Falls back to a `version.txt` at the project root.

[build]
mode = "portable"
os = "windows"
console = true
python = "3.12"
clean = false

[build.dependencies]
# auto = true means import-detected deps union with [project].dependencies
# and `include`. Version pins in [project].dependencies are preserved.
auto = true
exclude = []
include = []

# Per-entry PE subsystem override. Without this block, Coil auto-detects
# console vs GUI from imports.
[build.entries.update_checker]
subsystem = "console"

# VERSIONINFO stamped into each produced exe. Shared fields apply to all
# entries; per-entry overrides under [build.versioninfo.entries.<stem>].
[build.versioninfo]
company_name    = "Acme Corp"
product_name    = "MyApp"
legal_copyright = "Copyright (c) 2026 Acme Corp"
comments        = "Built by the platform team"   # Optional free-form metadata

[build.versioninfo.entries.main]
file_description = "MyApp Main Tool"

[build.versioninfo.entries.update_checker]
file_description = "MyApp Update Checker"
internal_name    = "myapp-updater"
comments         = ""                            # Suppress shared comments for this entry

[build.output]
dir = "./dist"
icon = ""

# Profiles override [build] settings
# [profile.dev]
# mode = "bundled"
# secure = false
# verbose = true

# [profile.release]
# mode = "portable"
# secure = true

Priority order: CLI flags > profile values > [build] values > defaults.

Stems with spaces in [build.versioninfo.entries.<stem>] and [build.entries.<stem>] require quoted TOML keys: [build.entries."My Tool"].

Per-entry overrides explicitly win, even when set to an empty string. Declaring comments = "" on a single entry suppresses the shared comments for just that exe — useful when one helper shouldn't carry the same metadata as the rest. Same rule applies to every optional VERSIONINFO field.

CLI Reference

Subcommands

Command Description
coil build <project> Build a Python project into an executable
coil init [project] Generate a coil.toml config file
coil doctor [project] Run pre-build diagnostics
coil inspect [project] Preview what Coil will include in a build
coil decompile <exe> Recover source from a default Coil build
coil cache info Show cache location and size
coil cache clear Delete all cached runtimes

coil --version prints the installed version. coil inspect accepts --python, --requirements, --exclude, --include, and --profile, so a preview can be taken under the same settings the build will use. coil doctor accepts --verbose.

Build Flags

Flag Default Description
--entry Auto-detect (__main__.py then main.py) Entry point script relative to project dir. Repeat for multiple entries
--mode portable portable (single exe) or bundled (directory)
--os Current OS windows, macos, linux
--python Auto-detect Target Python version
--gui Auto-detect from imports Suppress console window
--console Auto-detect from imports Show console window. Overrides auto-detected GUI mode
--secure false Heavy obfuscation, not reversible
--clean false Build in clean environment with only declared deps
--profile None Build profile from coil.toml
--exclude None Comma-separated packages to exclude
--include None Comma-separated packages to force-include
--output ./dist Output directory
--name Project dir name Output executable name
--icon None Icon file path (.ico)
--requirements Auto-detect Path to requirements.txt or pyproject.toml
--optimize 1 (normal) / 2 (secure) Bytecode optimization level (0, 1, or 2)
--verbose false Detailed build output
--dry-run false Preview build without executing

Obfuscation

Default mode: Source is compiled to bytecode and packaged with metadata that coil decompile can use to recover the original .py files. This is a safety net — not cryptographic security, but your source isn't casually visible.

Secure mode (--secure): Bytecode only, debug info stripped, no recovery metadata. coil decompile will refuse to process it. This is stronger but not impenetrable — no bytecode obfuscation is NSA-proof. It's meant to raise the bar, not guarantee secrecy.

FAQ

Q: Does Coil require Python on the target machine? No. Coil bundles an embedded Python runtime. The resulting executable is fully standalone.

Q: What about C extensions / native modules? Coil bundles .pyd / .dll files from installed packages. Most packages with C extensions work out of the box.

Q: How big are the executables? A minimal project produces an exe around 15-20 MB (mostly the Python runtime). Dependencies add to that. Coil strips unnecessary files to keep size reasonable.

Q: Can I cross-compile for other platforms? Not yet. Currently Coil only builds Windows executables on Windows. Cross-platform and cross-compilation are on the roadmap.

Q: What's the difference between portable and bundled? Portable = single .exe file you can copy anywhere. On first launch, it extracts the runtime to a local cache and runs from there. Later launches reuse the cache and start instantly. Bundled = directory with the executable and its supporting files. No extraction step — it runs directly from the directory.

Q: Where does the portable exe store its cache? %LOCALAPPDATA%\coil\<AppName>\<build_hash>\. Run coil cache info to see details, or coil cache clear to remove it. If %LOCALAPPDATA% isn't available, it falls back to %TEMP% or the exe's own directory.

Q: Is the portable exe really a single file? Yes. The build output is a single .exe. On first run, it extracts a cached copy of the runtime to a local directory. This is not visible to the user as a separate step — the app just starts. Subsequent runs skip extraction entirely.

Roadmap

  • macOS .app support
  • Linux ELF binary support
  • ARM64 bootloader for Windows on ARM
  • Cross-compilation

Contributing

Contributions welcome. Open an issue or submit a pull request.

# Development setup
git clone https://github.com/nathannncurtis/coil.git
cd coil
pip install -e .
python -m pytest

License

GNU General Public License v3.0 (or later). See LICENSE for the full text.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

coil_compiler-0.3.1.tar.gz (332.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

coil_compiler-0.3.1-py3-none-any.whl (291.1 kB view details)

Uploaded Python 3

File details

Details for the file coil_compiler-0.3.1.tar.gz.

File metadata

  • Download URL: coil_compiler-0.3.1.tar.gz
  • Upload date:
  • Size: 332.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.12.14

File hashes

Hashes for coil_compiler-0.3.1.tar.gz
Algorithm Hash digest
SHA256 69ed558fd9dadb08687bbf7981392dc9c8a6252b4b220635161ed9c98c9b0c63
MD5 40ef6ed3cce8cf5a91e93cf0b8ea44f0
BLAKE2b-256 f6a4fe37157a6efbf6e09959ce5e89b2736dbd6162cfd57fe4f0600fe5f34b82

See more details on using hashes here.

File details

Details for the file coil_compiler-0.3.1-py3-none-any.whl.

File metadata

  • Download URL: coil_compiler-0.3.1-py3-none-any.whl
  • Upload date:
  • Size: 291.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.12.14

File hashes

Hashes for coil_compiler-0.3.1-py3-none-any.whl
Algorithm Hash digest
SHA256 d13a4db833c10359765e10f383adc6aff60f92002e2f176dc8ca86e3fb9feb33
MD5 f605313a9ea54fb1d70bf4167de689df
BLAKE2b-256 9a490179b4a1d792f1673fc90cd584b7e0a54ecfc5dd9d9be356197f3fbc8ce5

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.3.1 This release

2 files

0.3.0

2 files

0.2.4

2 files

0.2.3

2 files

0.2.2

2 files

0.1.3

2 files

0.1.2

2 files

0.1.1

2 files

0.1.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page