mirror of
https://github.com/github/awesome-copilot.git
synced 2026-04-30 12:15:56 +00:00
3.4 KiB
3.4 KiB
Deploying Copilot SDK Apps with PyInstaller
Package a Copilot SDK application into a standalone executable using PyInstaller (or Nuitka).
Problem
When you freeze a Python SDK application with PyInstaller, three things break:
- CLI binary resolution — The SDK locates its CLI via
__file__, which points inside the PYZ archive in a frozen build. - SSL certificates — On macOS, the frozen app can't find system CA certs, so the CLI subprocess fails TLS handshakes.
- Execute permissions — The bundled CLI binary may lose its
+xbit when extracted from the archive.
Solution
Resolve the CLI path by searching both the SDK's normal location and PyInstaller's _MEIPASS temp directory. Fix SSL by injecting certifi's CA bundle into the environment. Restore execute permissions on Unix before launching.
"""Frozen-build compatibility for Copilot SDK applications."""
import os, sys
from pathlib import Path
from copilot import CopilotClient, SubprocessConfig
def resolve_cli_path() -> str | None:
"""Find the Copilot CLI binary in a frozen build."""
candidates = []
binary = "copilot.exe" if sys.platform == "win32" else "copilot"
# 1. SDK's normal resolution
try:
import copilot as pkg
candidates.append(Path(pkg.__file__).parent / "bin" / binary)
except Exception:
pass
# 2. PyInstaller _MEIPASS fallback
if getattr(sys, "frozen", False) and hasattr(sys, "_MEIPASS"):
meipass = Path(sys._MEIPASS)
candidates.append(meipass / "copilot" / "bin" / binary)
candidates.append(meipass.parent / "copilot" / "bin" / binary)
for c in candidates:
if c.exists():
if sys.platform != "win32" and not os.access(str(c), os.X_OK):
os.chmod(str(c), c.stat().st_mode | 0o755)
return str(c)
return None
def ensure_ssl_certs():
"""Set SSL env vars for the CLI subprocess (macOS frozen builds)."""
if os.environ.get("SSL_CERT_FILE"):
return
try:
import certifi
ca = certifi.where()
if Path(ca).is_file():
os.environ["SSL_CERT_FILE"] = ca
os.environ["REQUESTS_CA_BUNDLE"] = ca
os.environ.setdefault("NODE_EXTRA_CA_CERTS", ca)
except ImportError:
pass # CLI will use platform defaults
async def create_frozen_client():
"""Create a CopilotClient that works in both normal and frozen builds."""
ensure_ssl_certs()
kwargs = {"log_level": "info", "use_stdio": True}
if getattr(sys, "frozen", False):
cli = resolve_cli_path()
if cli:
kwargs["cli_path"] = cli
client = CopilotClient(SubprocessConfig(**kwargs), auto_start=True)
await client.start()
return client
PyInstaller Spec
Include the SDK's binary directory in your .spec file so PyInstaller bundles it:
from PyInstaller.utils.hooks import collect_data_files
data += collect_data_files('copilot', include_py_files=False)
Tips
- Test the frozen build on a clean machine —
_MEIPASSextraction behaves differently than your dev environment. - Pin
certifiin your requirements so the CA bundle is always available. - Nuitka uses a different extraction model (
--include-package-data=copilot), but the sameresolve_cli_pathlogic works.
Runnable Example
See recipe/pyinstaller_frozen_build.py for a complete working example.