Building an executable
ecko build app.ecko # -> ./app
ecko build app.ecko -o server # choose the name
Produces a single self-contained executable: the interpreter and your program in one file, with no Ecko installation required on the target.
What ends up inside
Your program, the modules it imports, its vendor/ tree, the runtime, and any files ecko.json lists under embed (below).
With an ecko.json, the bundle holds every .ecko file in the project. Without one it holds the entry file plus every file it reaches through relative imports (import "./src/args.ecko", export * from "./x.ecko"), followed recursively. If one of those imports names a file that does not exist, the build stops with an error naming the file and the import instead of producing a binary that fails at startup. A file imported by a path computed at run time cannot be found by reading the code, so it is not bundled: add an ecko.json (ecko init) to bundle the whole project. The standard library is already part of the binary, so a program that serves HTTP and queries SQLite has no external dependency to install.
Embedding files
Code is always bundled. Other files - a web app's static/, HTML templates, SQL migrations, fixture data - are bundled when ecko.json lists them:
{
"version": "0.1.0",
"entrypoint": "main.ecko",
"embed": ["static/**", "templates/*.html"]
}
Patterns are fs.glob's: * within one path segment, ** across segments, ? one character. They are relative to the project root and must stay inside it. A pattern that matches no file stops the build, so a typo cannot ship a binary that is missing a file. Dotfiles are never embedded, nor is an executable an earlier ecko build made, so "*" does not carry the last build. A symlinked file is embedded as the file it points to, as long as it points inside the project: a link out of it, or to a file that is never embedded such as .env, stops the build and names the link. ecko build says how many files it embedded, and ecko pack carries the same files.
The built program reads them through the functions it already uses, at the paths it uses when run from source. With the web template's web.static("/static", "static") and "embed": ["static/**"], the binary serves its stylesheet from any directory:
import std.fs
import std.web
page = fs.read("templates/page.html") # from the binary
routes = web.router([web.static("/static", "static")])
How reads behave in a built program:
- Only listed files come from the binary. Every other path reads the disk as usual,
ecko.jsonincluded. - An embedded file wins over the disk. A file of the same name next to the binary is not read, so a deploy behaves the same on every machine. For a file you mean to override at deploy time, leave it out of the list.
- Relative paths, and absolute ones under the working directory, both name an embedded file.
fs.join(os.cwd(), "static")works, andfs.canonicalof an embedded file gives a path that reads it back. - Directories show both.
fs.list_dir,fs.walkandfs.globlist the embedded files together with whatever is on disk there, so files the program writes beside them still appear.fs.mkdirand new files inside an embedded directory work as they do from source. - Embedded files are read-only. Writing, appending, removing, renaming, copying onto,
chmodorset_modifiedon one is an error of kindfs, and so isjson.write,csv.write,image.save, creating or extracting an archive onto one, or pointing alogfile sink at one:fs.write: 'static/a.css' is embedded in this build and read-only. From source the same write succeeds, which is the one place the two differ. fs.modifiedreports the binary's own time for an embedded file, so two builds of the same files are identical byte for byte.
Not read from the binary: sql.open (SQLite needs a real file it can lock), the certificate and key files given to http.serve, the input files of archive.zip_create and archive.tar_create, and files a package reads for itself (ecko get warns about a package that declares embed).
The files count toward the same size limit as the rest of the bundle (ECKO_PKG_MAX_UNPACKED, 200 MB by default); a build over it stops with both numbers.
What it is for
Handing someone a tool. A colleague, a container, a machine you do not control: one file, chmod +x, run. No language to install, no version to match, no pip install step that fails in six months.
What it is not
Not a compiler to native code. The program is still interpreted by the bundled runtime, so ecko build changes distribution, not performance. A program does not get faster by being built.
Not a cross-compiler either. The output runs on the platform you built it on. Build on Linux for Linux, on macOS for macOS - which for CI means a job per target.
Size
The runtime is the floor, so a five-line program and a five-thousand-line program produce executables of similar size. If size matters more than convenience, ship the script and install ecko once.
Containers
For a server, a container is usually the better unit than a bare executable - same self-containment, plus the surrounding environment. See Docker.