C6 — fitz build (compilar a binario nativo)¶
Pre-requisitos: C5 — REPL terminado. Conocés todo el ciclo de desarrollo interpretado.
Objetivo: dominar fitz build con todos sus flags
(--bundle-python, --bundle-pip, --bundle-pip-requirements),
entender el pipeline de compilación (Fitz → Rust → LLVM →
binario), saber la estructura del target/ dir, comparar cold
vs warm builds, y conocer las limitaciones de
cross-compilation.
Por qué importa: esto es el diferencial de Fitz frente a
Python/JS/Ruby. Esos lenguajes te dan un script que necesita
el runtime del lenguaje en cada máquina donde corra. Fitz te da
un binario standalone — un solo archivo que copiás, corrés,
y listo. Sin instalar nada en el server, sin Docker base image
con Python, sin pip install.
El pipeline fitz build end-to-end¶
flowchart LR
A[hello.fitz<br/>código Fitz] --> B[lexer + parser]
B --> C[type checker<br/>strict]
C --> D[codegen<br/>Fitz → Rust]
D --> E[target/fitz-build/<name>/<br/>Cargo project]
E --> F[cargo build --release<br/>rustc + LLVM]
F --> G[hello.exe<br/>binario nativo standalone]
Tres etapas distintas:
| Etapa | Herramienta | Output | Tiempo típico |
|---|---|---|---|
| 1. Fitz pipeline | fitz mismo |
Rust source code en target/fitz-build/ |
<100ms |
| 2. Cargo project | fitz (orquesta) |
Cargo.toml + src/main.rs |
<50ms |
| 3. Compilación final | cargo build --release (rustc + LLVM) |
Binario nativo | 3-30s primera vez, <3s incremental |
El paso 3 es el costoso. Es real compilación LLVM-grade, el mismo backend que usa
rustc,swiftyclang.
Paso 1 — Sintaxis completa¶
| Posición / flag | Para qué |
|---|---|
FILE |
Archivo .fitz a compilar. Sin él, manifest mode (lee fitz.toml). |
--bundle-python |
Embebe CPython adentro del binario. Standalone hasta sin Python instalado en el destino. |
--bundle-pip <PACKAGE> |
Suma paquetes pip al bundle. Repetible. Implica --bundle-python. |
--bundle-pip-requirements <FILE> |
Suma paquetes desde un requirements.txt. Repetible. Implica --bundle-python. |
-h, --help |
Help. |
Pre-requisito del sistema: rustc¶
fitz build invoca cargo por debajo. Aunque fitz viene
pre-compilado, fitz build necesita Rust instalado:
# Verificar
rustc --version
# rustc 1.85.0 (o más reciente)
# Instalar si no tenés
# Linux/macOS:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# Windows: bajá de https://rustup.rs y corré rustup-init.exe
Es la única dep externa de fitz build. Sin rustc, el
comando aborta con error claro.
Paso 2 — Single-file mode¶
El caso más simple: compilar un archivo suelto, sin proyecto.
mkdir build-demo && cd build-demo
cat > hello.fitz << 'EOF'
let nombre = "Patagonia"
print("Hola desde {nombre}")
EOF
fitz build hello.fitz
Lo generado en el cwd:
build-demo/
├── hello.fitz ← tu fuente
├── hello.exe ← binario producido (Windows; en Linux: hello)
└── target/ ← work dir del compilador
Probalo:
El binario es standalone. Copialo a una máquina que no tenga Fitz y va a correr igual.
Paso 3 — Manifest mode¶
Desde la raíz de un proyecto creado con fitz new:
A diferencia del single-file mode, el binario NO queda
adyacente al fuente — va a target/release/, nombrado como
el paquete (mi-saludos.exe). Convención Cargo-style.
Probalo:
Tabla: diferencias entre los dos modos¶
| Aspecto | Single-file mode | Manifest mode |
|---|---|---|
| Comando | fitz build hello.fitz |
fitz build (sin args, desde proyecto) |
| Output path | ./hello.exe (adyacente al fuente) |
./target/release/<pkg-name>.exe |
| Nombre del binario | Stem del archivo (hello) |
[package].name del manifest |
| Workdir | ./target/fitz-build/<stem>/ |
./target/ |
| Útil para | Scripts sueltos, scratchpad | Proyectos serios, libs, deploys |
Paso 4 — Estructura del target/ dir¶
fitz build genera un mini-proyecto Cargo en target/ y
delega la compilación a cargo. Layout:
target/
├── fitz-build/ ← intermedios de Fitz
│ └── <name>/
│ ├── Cargo.toml ← deps del programa generado
│ └── src/
│ └── main.rs ← Rust generado (legible, inspeccionable)
└── release/ ← (manifest mode) output final
└── <pkg-name>.exe
El main.rs generado es legible¶
Para tu hello.fitz, el main.rs se ve así (resumido):
// Código generado por Fitz 5b — no editar a mano.
#![allow(unused_mut, unused_variables, ...)]
fn main() {
let mut nombre: String = String::from("Patagonia");
println!("{}", format!("Hola desde {}", nombre.clone()));
}
Es Rust idiomático. Podés inspeccionarlo si querés entender qué hace el codegen. Útil para debug y para aprender cómo Fitz mapea a Rust.
Cargo.toml generado¶
Para un programa sin deps:
[package]
name = "hello"
version = "0.1.0"
edition = "2021"
[[bin]]
name = "hello"
path = "src/main.rs"
Para un programa con features (HTTP, Python interop, async), suma deps automáticamente:
[dependencies]
axum = "0.8"
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }
serde_json = { version = "1", features = ["preserve_order"] }
# ... más deps según features
Paso 5 — Paridad bit-a-bit run ↔ build¶
Tu programa se comporta exactamente igual interpretado y compilado. No hay "modo dev" vs "modo prod" con semánticas distintas — Fitz garantiza paridad bit-a-bit.
flowchart TD
A[Mismo .fitz] --> B[fitz run<br/>interpretado]
A --> C[fitz build<br/>compilado a binario]
B --> D[Mismo output<br/>bit-a-bit]
C --> D
fitz run src/main.fitz
# Saludos desde Patagonia
./target/release/mi-saludos.exe
# Saludos desde Patagonia
Igual output. Esto es decisión de diseño fuerte: lo que ves
en el LSP es lo que ves en fitz run es lo que ves en el
binario final.
Por qué importa¶
- CI: si
fitz checkpasa, elfitz buildtambién pasa (mismas reglas). - Debug: si el binario tiene un bug, podés reproducirlo con
fitz runy debuggearlo con menos overhead. - Confianza: no hay "funciona en dev pero rompe en prod" por divergencia de runtime.
Paso 6 — Tiempos de build (cold vs warm)¶
Tabla: tiempos esperados¶
| Caso | Cold | Warm |
|---|---|---|
| Hello-world (sin deps) | <1s | <500ms |
| CLI con tipos custom | <2s | <1s |
| HTTP server (axum+tokio) | 60-120s | 2-5s |
| HTTP + auth + WS + DB | 90-180s | 3-8s |
Con --bundle-python |
+ 30-60s una vez | + 0s (cache) |
El cache vive en
target/. NO lo borres si querés warm builds. Si necesitás full rebuild:rm -rf target/(oRemove-Item -Recurse target/en Windows).
Paso 7 — Cuándo run vs build¶
flowchart TD
A{¿Qué necesito?} --> B[Iterar rápido en dev]
A --> C[Distribuir a producción]
A --> D[Validar la versión final antes del deploy]
A --> E[Test suite del proyecto]
B --> F[fitz run<br/>arranca en ms]
C --> G[fitz build<br/>binario standalone]
D --> G
E --> H[fitz test]
Tabla detallada:
| Caso | Comando | Por qué |
|---|---|---|
| Iterar mientras desarrollás | fitz run |
Arranca en ms, sin compilación |
| Validar tipos sin ejecutar | fitz check |
El más rápido |
| Hot reload tras cada save | fitz dev |
Re-arranca solo |
| Correr tests | fitz test |
Cubre @test fns |
| Probar la versión final antes de shippear | fitz build + run del binario |
Ves exactamente lo que el deploy verá |
| Distribuir tu programa a otros | fitz build + copiar binario |
Standalone, sin instalar nada |
| CI / artefactos del release | fitz build |
Lo que vas a publicar |
Regla simple: usá fitz run para todo lo interactivo;
fitz build cuando vayas a shippear.
Paso 8 — Cross-compilation¶
Como Fitz tira de rustc por debajo, podés cross-compilar
gratis a cualquier target Rust.
Cómo¶
- Instalar el target:
rustup target add x86_64-unknown-linux-gnu
rustup target add aarch64-apple-darwin
rustup target add x86_64-pc-windows-msvc
- Configurar el target (por ahora, requiere editar el
Cargo.tomlgenerado o setearCARGO_TARGET=<triple>):
Limitación actual:
fitz buildno expone un flag--targetdirecto. Es deuda del CLI — pasa por env vars de cargo. Esto se va a refinar.
Tabla de targets típicos¶
| Target Rust | Plataforma |
|---|---|
x86_64-unknown-linux-gnu |
Linux x64 |
aarch64-unknown-linux-gnu |
Linux ARM64 (Raspberry Pi, AWS Graviton) |
x86_64-apple-darwin |
macOS Intel |
aarch64-apple-darwin |
macOS Apple Silicon (M1+) |
x86_64-pc-windows-msvc |
Windows x64 |
Limitaciones de cross-compile¶
| Cross | Funciona? | Caveat |
|---|---|---|
| Linux → Linux ARM | ✅ | Necesitás linker ARM (apt install gcc-aarch64-linux-gnu) |
| Linux → Windows | ⚠️ | Necesitás mingw-w64. Algunos crates fallan. |
| Linux → macOS | ❌ | Requiere SDK de Apple (legal/técnico — usá CI con runner macOS) |
| macOS → Linux | ✅ | Igual que Linux→Linux ARM en cuanto a linker |
| macOS Intel → Apple Silicon (o vice-versa) | ✅ | Más fácil con aarch64-apple-darwin |
| Windows → Linux | ⚠️ | Posible con WSL o cross containers |
📚 Detalle de cross-compile: M7 (Producción y deployment).
Paso 9 — Flag --bundle-python (interop Python)¶
Cuando tu programa usa from python import ... (interop con
Python, cubierto en módulo del lenguaje específico), el binario
final necesita CPython en runtime. Dos opciones:
| Opción | Cómo | Pro | Contra |
|---|---|---|---|
| Asumir Python instalado en destino | fitz build (sin flag) |
Binario chico (~125 KB para hello) | Falla si destino no tiene Python |
| Embeber CPython en el binario | fitz build --bundle-python |
Standalone hasta sin Python | Binario pesa ~30 MB (Win) / ~45 MB (Linux x64) |
--bundle-python solo¶
Internamente, --bundle-python baja Python 3.14 standalone via
python-build-standalone
(proyecto de Astral, el equipo de uv), lo cachea en
~/.fitz/cache/python/, y lo embebe.
--bundle-pip <PACKAGE> (repetible)¶
Sumar paquetes pip al bundle:
Implica --bundle-python automáticamente (sin Python
embebido no tiene sentido bundlear pip).
Sintaxis del valor:
| Sintaxis pip | Ejemplo |
|---|---|
| Nombre simple | --bundle-pip requests |
| Version exacta | --bundle-pip "requests==2.31.0" |
| Version range | --bundle-pip "requests>=2.30,<3.0" |
| Extras | --bundle-pip "fastapi[all]" |
| Git | --bundle-pip "git+https://github.com/foo/bar.git" |
--bundle-pip-requirements <FILE> (repetible)¶
Lee paquetes desde un requirements.txt estándar:
requirements.txt:
Toda la sintaxis nativa de pip (incluyendo -r other.txt,
--hash, #egg=, etc.) funciona — el flag pasa el archivo
directo a pip install -r <file>.
Combinable con --bundle-pip:
pip recibe ambos.
Paso 10 — El CLI completo de Fitz (inventario)¶
Llegaste al final de M1 y conocés los comandos del día-a-día. Acá el mapa completo para que sepas qué existe y dónde se cubre:
| Comando | Para qué | Dónde se cubre |
|---|---|---|
fitz new |
Crear proyecto nuevo | M1.C2 ✅ |
fitz init |
Inicializar proyecto en cwd | M1.C2 ✅ |
fitz run |
Ejecutar (interpretado) | M1.C2, M1.C4 ✅ |
fitz check |
Type-check sin ejecutar | M1.C4 ✅ |
fitz fmt |
Formatear código | M1.C4 ✅ |
fitz lint |
Detectar patrones malos | M1.C4 ✅ |
fitz dev |
Hot reload | M1.C4 ✅ |
fitz repl |
REPL interactivo | M1.C5 ✅ |
fitz build |
Compilar a binario | M1.C6 ✅ ← acá |
fitz test |
Correr @test fns |
M2.C6 |
fitz add |
Agregar dependencia | M3 |
fitz remove |
Quitar dependencia | M3 |
fitz update |
Actualizar git deps | M3 |
fitz openapi |
Emitir schema OpenAPI 3.1 | M4 |
fitz db |
Migraciones del ORM | M6 |
fitz py-types |
Generar type Fitz desde modelos SQLAlchemy |
(referencia exhaustiva en guide, no cubierto en curso) |
fitz py-stubs |
Generar type Fitz desde stubs .pyi |
(idem) |
fitz --help te lista todo en cualquier momento.
Paso 11 — Optimización del binario¶
Estrategias para reducir tamaño¶
Para un hello-world, ~125 KB ya es chico. Para programas con HTTP/axum, el binario base es ~5-8 MB. Para más reducción:
| Técnica | Reducción típica | Comando |
|---|---|---|
strip (quitar debug symbols) |
-20-30% | strip hello.exe (Linux/macOS) |
upx (compresión binaria) |
-50-70% | upx --best hello.exe |
Editar Cargo.toml generado con [profile.release] agresivo |
Variable | Requiere tocar target/fitz-build/.../Cargo.toml (deuda — no expuesto) |
Hoy fitz build no expone flags para esto. Si necesitás
binarios super-chicos (deployment a edge, embedded), tocás el
Cargo.toml generado a mano y rebuild con cargo directo.
Comparativa de runtime (cold start)¶
| Programa | Cold start típico |
|---|---|
| Hello-world | <10ms |
| CLI con parsing | <30ms |
| HTTP server (axum) | ~50-100ms |
| HTTP + DB connection pool | ~200-500ms |
Comparado con:
| Comparación | Cold start |
|---|---|
Python script.py |
~30-50ms (sin imports) |
| Python con FastAPI + SQLAlchemy | ~1-3s |
Node.js script.js |
~50-100ms |
| Java/Spring | ~5-10s |
Fitz binarios son cercanos al overhead mínimo del sistema operativo. Eso es el punto de compilar a nativo.
Paso 12 — Aplicarlo a mi-saludos¶
Volvé al proyecto del curso:
Probá:
Listo. Tenés un binario standalone de tu proyecto. Copialo a otra máquina sin Fitz instalado y va a correr igual.
Comparativa con fitz run¶
Para un hello-world, ambos son rápidos. La diferencia se nota en programas grandes — el binario nativo escala mejor (sin overhead del checker en runtime).
Paso 13 — Limitaciones MVP de fitz build¶
| Feature | Estado | Workaround |
|---|---|---|
| Single-file mode | ✅ | — |
| Manifest mode | ✅ | — |
| Cargo project generado inspeccionable | ✅ | — |
--bundle-python |
✅ | — |
--bundle-pip + --bundle-pip-requirements |
✅ | — |
--target <triple> directo |
❌ | Usá CARGO_BUILD_TARGET env var |
--profile dev (debug build) |
❌ | Editá Cargo.toml generado |
--release (es el default) |
✅ implícito | Único modo soportado |
| Watch + auto-rebuild | ❌ | Usá fitz dev (no compila, interpreta) |
Output dir custom (-o <path>) |
❌ | Mover/copiar manualmente |
| Cross-compile cómodo | ⚠️ | Via env vars + targets de rustup |
cargo features custom |
❌ | Editá Cargo.toml generado |
Validación¶
-
fitz build hello.fitz(single-file) producehello.exeadyacente al fuente. - El binario corre sin Fitz instalado (probalo: copialo a una carpeta sin nada y corrélo).
-
fitz build(manifest mode, desdemi-saludos/) deja el binario entarget/release/mi-saludos.exe. - El output del binario coincide bit-a-bit con
fitz run. - Cold build de hello-world toma <2s; warm build <500ms.
Troubleshooting¶
El primer fitz build tarda 30+ segundos¶
Normal. fitz build invoca cargo por debajo, y el primer
build compila toda la base del runtime de Fitz emitido (axum,
tokio, serde, etc. dependiendo de qué use tu programa). Las
builds siguientes son incrementales y mucho más rápidas
(típicamente <3s para cambios chicos).
error: linker 'cc' not found (Linux)¶
Fitz necesita un compilador C/C++ para linkear el binario final:
# Debian/Ubuntu
sudo apt install build-essential
# Fedora
sudo dnf install gcc
# Arch
sudo pacman -S base-devel
error: cannot find 'rustc'¶
Aunque fitz viene como binario pre-compilado, fitz build
necesita rustc instalado porque transpila a Rust. Instalalo
con rustup.
El binario funciona en mi máquina pero falla en el server¶
- Verificá la arquitectura del server:
uname -m. Si compilaste en x64 y el server es ARM (Raspberry Pi, AWS Graviton), necesitás cross-compilar. - En Linux, si el server tiene una glibc más vieja que la tuya (compilaste en Ubuntu 24.04, server es Debian 11), el binario puede fallar al cargar. Workarounds:
- Compilar en una distro vieja.
- Usar un container con la glibc target.
- Compilar contra musl:
--target x86_64-unknown-linux-musl.
--bundle-python baja Python cada vez (lento)¶
Primer build sí baja Python (~30MB). Quedan cacheados en
~/.fitz/cache/python/ y reused en builds siguientes. Si lo
borraste, va a bajar de nuevo.
El binario es muy grande (5+ MB)¶
Para programas HTTP es esperado (axum + tokio + serde son
varios MB cada uno). Para reducir:
- strip: strip mi-app (Linux/macOS).
- upx: upx --best mi-app.
- Si necesitás <1 MB, mirá si podés simplificar las features
(¿realmente necesitás auth + WS + Python interop + ORM?).
Cómo veo el código Rust generado¶
# Single-file mode:
cat target/fitz-build/<stem>/src/main.rs
# Manifest mode:
cat target/fitz-build/<pkg-name>/src/main.rs
Es Rust legible. Útil para debug y para aprender mapeo Fitz → Rust.
Cerraste el módulo M1¶
Felicidades, llegaste al final del módulo de Setup. Sabés:
- ✅ Instalar Fitz + extensión VSCode (C1)
- ✅ Crear un proyecto con
fitz newy entender su anatomía completa (C2) - ✅ Editar Fitz en VSCode con hover, autocomplete contextual (4 modos), diagnostics live, go-to-def, panel Problems (C3)
- ✅ Usar
run,check,fmt,lint,devcon todos sus flags + workflows pre-commit/CI (C4) - ✅ Experimentar interactivo con
fitz repl+ sus 6 comandos especiales (C5) - ✅ Compilar a binario nativo standalone con
fitz buildy conocer todos los flags incluido--bundle-python(C6) ← acá
Entregable del módulo: tenés Fitz instalado, un proyecto que escribís, corrés, formateás, debugeás, y compilás a binario standalone para distribuir.
Qué viene en M2 — Tipos y funciones¶
A partir del próximo módulo dejamos el "setup" y entramos al lenguaje en serio. M2 cubre el sistema de tipos gradual:
- C1 — Primitivos, strings e interpolación
- C2 — Variables, anotaciones e inferencia
- C3 — Operadores y control de flujo (
if/else) - C4 — Loops (
while,loop,for in) - C5 — Listas, mapas y rangos
- C6 — Funciones +
fitz test - C7 —
type(tipos custom) +match
Es la base sobre la que se construye todo lo demás del lenguaje. Empezá por M2.C1.