A Python-to-executable compiler that just works.
Project description
Coil
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 (or reads your requirements.txt)
- Bundles an embedded Python runtime — no Python installation needed on the target machine
- Produces a single portable .exe or a clean bundled directory
- 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
coilisn't recognized after install, your pip Scripts directory may not be on PATH. You can either:
- Use
python -m coilinstead (works the same way — all examples below apply)- Or add Python's Scripts directory to your PATH (
python -m site --user-sitewill 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
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, or plyer, 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
Multiple Entry Points
Each entry point produces its own executable:
coil build ./myproject --entry cli.py --entry gui.py --mode bundled
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 decompiled.
Clean Build
Build in an isolated environment with only declared dependencies. Guarantees reproducible builds:
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.
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
-
Dependency Resolution — Coil checks for a
requirements.txtorpyproject.toml. If neither exists, it scans every.pyfile using Python'sastmodule to find all imports, separates stdlib from third-party, and resolves import names to PyPI packages. -
Runtime Bundling — Downloads the official Windows embeddable Python distribution matching your target version. No C compiler needed.
-
Compilation — All
.pyfiles are compiled to.pycbytecode. No loose.pyfiles in the output. -
Packaging — In portable mode, everything is packed into a single
.exefile. In bundled mode, a clean directory with the runtime, compiled code, and dependencies.
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
.execontains a lightweight native launcher (~23 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 detect the cache and start instantly — no extraction needed.
- Each build gets a unique hash. Rebuilding your app creates a new cache entry. Old cache entries are automatically cleaned up (only the 3 most recent are kept).
- 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.txtorpyproject.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"
[build]
mode = "portable"
os = "windows"
console = true
python = "3.12"
clean = false
[build.dependencies]
# Coil resolves deps automatically from requirements.txt or AST scan
auto = true
exclude = []
include = []
[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.
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 |
Build Flags
| Flag | Default | Description |
|---|---|---|
--entry |
Auto-detect (__main__.py then main.py) |
Entry point script relative to project dir |
--mode |
portable |
portable (single exe) or bundled (directory) |
--os |
Current OS | windows, macos, linux |
--python |
Auto-detect | Target Python version |
--gui |
false |
Suppress console window |
--console |
true |
Show console window (default) |
--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 |
--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
- Optional runtime signing (helps with AV false positives)
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
Apache License 2.0. See LICENSE for the full text.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file coil_compiler-0.1.0.tar.gz.
File metadata
- Download URL: coil_compiler-0.1.0.tar.gz
- Upload date:
- Size: 83.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.10
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a8f2b0b7cad61e76f3563624a2bf3cb7c91907d04d7cdb64682dcb05ff3abcc5
|
|
| MD5 |
d05574e42352229a5aad61b6b6eef0b3
|
|
| BLAKE2b-256 |
7e6d17ff872aaccde02a40692d40f58f33b99c2c61c289b2ae51418de65873ff
|
File details
Details for the file coil_compiler-0.1.0-py3-none-any.whl.
File metadata
- Download URL: coil_compiler-0.1.0-py3-none-any.whl
- Upload date:
- Size: 75.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.10
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
00eb8b0bf10d55ce4cfb308fa303fd24263d2d6a924ca4030769fd52cca30cf9
|
|
| MD5 |
223448488acf8fc428285c6ae7944395
|
|
| BLAKE2b-256 |
3e870312ad2d00d33264cd3b4335786feca5936b25704715a1d3cc66f85055d6
|