Bundling And Standalone Executables
geblang build produces a self-contained binary from a Geblang package. The
resulting file runs on any machine with the same OS and architecture as the
machine that built it. No Geblang installation, no stdlib directory, and no
geblang.yaml manifest are required on the target machine.
How Bundling Works
A Geblang bundle is a standard executable with a zip archive appended to it.
┌──────────────────────────────────────────────────┐
│ geblang interpreter binary (ELF/Mach-O/PE) │
├──────────────────────────────────────────────────┤
│ zip archive │
│ BUNDLE.json manifest │
│ src/app/main.gb source files │
│ src/app/main.gbc precompiled bytecode │
│ stdlib/collections.gb bundled source stdlib │
│ stdlib/collections.gbc │
│ ... │
├──────────────────────────────────────────────────┤
│ 8-byte zip size (little-endian uint64) │
│ 4-byte magic: GEBX │
└──────────────────────────────────────────────────┘
At startup the interpreter checks whether a 12-byte trailer is present and
whether it contains the GEBX magic value. If so, it reads the zip, parses
BUNDLE.json, and runs the bundled entry module instead of looking for
command-line arguments.
The zip archive is a standard zip file and can be inspected with unzip -l or
any zip reader.
What Gets Bundled
geblang build walks the import graph starting from the entry module and
collects every non-native module it can reach:
- User source modules are included under
src/inside the zip. - Imported package dependencies declared in
geblang.yamlare included when they are installed undervendor/<name>/and reached by the import graph. - Source-stdlib modules (Geblang-written standard library files) are
included under
stdlib/inside the zip. - Native modules - Go-backed modules like
io,sys,collections,http,db, and so on - are part of the interpreter binary itself and are not duplicated in the zip.
Each collected source file is also compiled to bytecode and stored alongside it
as a .gbc file. The precompiled bytecode is loaded directly into the
interpreter's bytecode cache on first run, so the bundled program starts at
full speed with no warm-up compilation step.
Only imported modules are bundled. geblang build does not copy the entire
package directory, the entire vendor/ directory, test files, generated
artifacts, or arbitrary assets. If a package dependency is declared but no
module from that package is imported, it is not included in the bundle.
Package Layout
A bundle is built from a Geblang package. The package needs:
- A
geblang.yamlmanifest that declares the package name and source root. - At least one source module that exports a
main(list<string> args)function to serve as the entry point.
Minimal package layout:
myapp/
geblang.yaml
src/
myapp/
main.gb
geblang.yaml:
name: myapp
source: src
src/myapp/main.gb:
module myapp.main;
import io;
import sys;
export func main(list<string> args): void {
io.println("Hello from a bundled app!");
}
The entry module name must match a module declaration that the package
resolver can find. In this layout the canonical name is myapp.main.
Running geblang build
geblang build --entry <module.name> --out <output-path> [<package-dir>]
| Argument | Required | Description |
|---|---|---|
--entry |
yes | Canonical name of the entry module (must export main) |
--out |
yes | Path for the output binary |
<package-dir> |
no | Package root directory (default: .) |
Build the example package above:
geblang build --entry myapp.main --out ./dist/myapp ./myapp
Or from inside the package directory:
cd myapp
geblang build --entry myapp.main --out ../dist/myapp
The output binary is set executable (chmod 755). On Unix you can run it
directly:
./dist/myapp
Pass arguments to the bundled program the same way you would any other binary:
./dist/myapp --port 8080 --verbose
The argument list is forwarded to the entry module's main function via
sys.args().
Full Example: A CLI Tool
Package layout:
greet/
geblang.yaml
src/
greet/
main.gb
formatter.gb
geblang.yaml:
name: greet
source: src
src/greet/formatter.gb:
module greet.formatter;
export func format(string name): string {
return "Hello, " + name + "!";
}
src/greet/main.gb:
module greet.main;
import io;
import sys;
import greet.formatter as fmt;
export func main(list<string> args): void {
if (args.length() == 0) {
io.println("Usage: greet <name>");
sys.exit(1);
}
io.println(fmt.format(args[0]));
}
Build and run:
geblang build --entry greet.main --out ./greet-bin ./greet
./greet-bin Ada
# Hello, Ada!
Both greet.main and greet.formatter are discovered automatically by the
import-graph walk and bundled together.
Bundling Package Dependencies
Geblang's package installer places git dependencies under vendor/. Bundling
uses the same package resolver as normal execution, so imported dependency
modules are included automatically once they are installed.
Example application manifest:
name: webapp
source: src
dependencies:
authlib:
git: https://example.com/authlib.git
version: main
After running geblang install, the dependency is available at:
webapp/
geblang.yaml
src/
main.gb
vendor/
authlib/
geblang.yaml
src/
authlib/
tokens.gb
Application code can import it normally:
module webapp.main;
import io;
import authlib.tokens as tokens;
export func main(list<string> args): void {
io.println(tokens.issue("ada"));
}
Build the application:
geblang build --entry webapp.main --out ./webapp-bin ./webapp
The resulting executable contains webapp.main, authlib.tokens, any other
non-native modules reached from those imports, and the source stdlib modules
they need. The target machine does not need vendor/, geblang.yaml, or a
separate Geblang installation.
geblang build does not fetch missing dependencies. If geblang.yaml declares
a git dependency and vendor/<name>/ is absent, run geblang install before
building.
Full Example: A Web Server
webapi/
geblang.yaml
src/
webapi/
main.gb
routes.gb
geblang.yaml:
name: webapi
source: src
src/webapi/routes.gb:
module webapi.routes;
import web;
export func register(web.Router router): void {
router.get("/", func(web.Request req, web.Response res): void {
res.json({"status": "ok"});
});
}
src/webapi/main.gb:
module webapi.main;
import io;
import sys;
import web;
import webapi.routes as routes;
export func main(list<string> args): void {
let port = args.length() > 0 ? args[0] : "8080";
let app = web.app();
routes.register(app.router());
io.println("Listening on :" + port);
app.listen(":" + port);
}
Build a distributable API server:
geblang build --entry webapi.main --out ./webapi-server ./webapi
./webapi-server 9000
First-Run Extraction
On the first invocation of a bundled binary, the zip is extracted to a temporary directory under the system temp path:
/tmp/geblang-<hash>/
src/
...
stdlib/
...
The directory name includes a SHA-256 hash of the zip contents, so different bundle versions use different directories and never collide. On subsequent runs, the extracted directory already exists and extraction is skipped - the startup overhead is paid only once.
Precompiled bytecode is loaded into the interpreter's bytecode cache at extraction time so recompilation is avoided even after the cache directory is cleared.
Inspecting A Bundle
Because the bundle is a valid zip appended to the binary, standard tools work:
# List bundled files
unzip -l ./dist/myapp
# Extract the source files manually
unzip ./dist/myapp -d ./bundle-contents
BUNDLE.json inside the zip records the entry module name, the Geblang version
the bundle was built with, and the canonical name, zip path, and source hash
for every bundled module.
Limitations
- OS and architecture: A bundle built on Linux/amd64 runs only on Linux/amd64. Cross-compilation is not yet supported; build on the target platform or use a cross-compilation container.
- No hot-reload: Bundled source is extracted once and cached. Changes to the original source files have no effect on a built bundle.
- Import-graph walking:
geblang builddiscovers modules by parsing import statements statically. Dynamic module loading (using string variables as import paths) is not currently traversed. Any module loaded this way must be imported explicitly elsewhere in the package so it is included in the bundle. - Vendored dependencies: imported modules from installed package
dependencies are bundled, but unimported files under
vendor/are not copied wholesale. - Native extensions: Go-backed
ext.*extensions are not bundled. They must be present on the target machine alongside the bundle binary.