The Build System
SuperJ ships a Cargo-inspired build tool built into the superj binary. For a single file you can still call superj compile … && clang … by hand (see Getting Started), but for anything with more than one source file or a dependency, use the build system: it owns the project layout, dependency resolution, linking, and a reproducible lockfile, so you never write a clang line yourself.
The build tool is superj itself — there is no separate toolchain to install and nothing else to run. It compiles your project in SuperJ, in-process, and links with your system C compiler. superj --help lists every command.
This page is the how-to; superj --help lists every subcommand and flag.
A first project
superj new hello
cd hello
superj run
superj new scaffolds:
hello/
Build.sj # the manifest (see below)
src/hello/Main.sj # package hello; public class Main { … main … }
.gitignore # ignores target/
superj run builds to target/debug/hello and executes it. superj build just builds; superj build --release uses the release profile.
The manifest — Build.sj
The manifest is ordinary SuperJ source at the project root, read by parsing (never executing) a public class Build for well-known static final fields:
public class Build {
static final String name = "hello";
static final String version = "0.1.0";
static final String spec = "1.0"; // language spec version targeted
static final String entry = "hello.Main"; // fully-qualified main class
// dependencies (all optional; see "Dependencies")
static final String[] dependencies = { "util", "../util" }; // path
static final String[] gitDependencies = { "json", "http://…/sj-json", "v1.0.0" }; // git
// release-profile knobs
static final String releaseSimd = "avx2";
static final boolean releaseBoundsCheck = false;
static final int releaseOpt = 3;
}
Source lives under src/, with the usual package x.y; ⇔ path rule (src/hello/Main.sj ⇒ package hello;). Unknown manifest fields are ignored, so newer manifests stay readable by older toolchains.
Commands
| Command | What it does |
|---|---|
superj new <name> | Scaffold a new project. |
superj build [--release] [--enterprise] | Compile + link to target/{debug,release}/<name>. |
superj run [--release] [-- <args>] | Build, then run the binary (args after -- are forwarded). |
superj check | Type-check the whole project — no codegen or link. |
superj test [-p <member>] | Compile the package's library into each // UNIT suite under test/ and run them (see Writing & Running Tests). |
superj add <name> <path> | Add a path dependency to Build.sj. (--git <url> --rev <rev> for a git dep.) |
superj clean | Remove target/. |
superj --help | List every compiler and build-system command. |
Dependencies
SuperJ compiles whole-program: a dependency's source is compiled into your program (there is no per-package binary ABI — that is why deps ship as source). Two kinds, both resolved into one merged compile:
- Path —
dependencies = { "util", "../util" }: another local project (its ownBuild.sj+src/), the path relative to this package. Resolution is recursive, with diamond de-duplication; a dependency cycle and a missing dependency are clear errors. - Git —
gitDependencies = { name, url, rev }: fetched at a pinned commit or tag into a content-addressed cache ($SUPERJ_CACHE_DIR, else~/.superj/cache) and then treated exactly like a path dep. A warm cache builds offline.
Each resolved dependency — its path or git url+commit, and a content hash — is recorded in superj.lock, which you commit for reproducible builds.
(A package registry — and superj publish — is intentionally out of scope: it's a separate project. Use path and git dependencies.)
Workspaces
Group several packages under one repository. Declare members in a root Build.sj (which has no name/entry of its own — just the member list):
public class Build {
static final String[] workspaceMembers = { "app", "libs/util" };
}
superj build/check/test from the workspace root act on all members (or one with -p <member>); superj run from the root needs -p <member> (or a single-member workspace). Run any command from inside a member directory and it acts on just that member, exactly like a standalone project. Members depend on each other as ordinary path deps, and a member that is a library (no entry) is type-checked rather than linked (it has no runnable artifact under whole-program compilation — it links into its consumers as source).
Profiles
dev (the default) builds with array bounds checks on and portable SIMD. --release applies the manifest's release* fields: releaseSimd → --simd, releaseBoundsCheck = false → --no-bounds-check, and releaseOpt → the link optimization level (clang -O<n>, clamped 0–3). The compiler always passes -O3 to clang at link time (unless --debug); releaseOpt is reserved for future fine-grained control.
Enterprise (whole-program LTO)
--enterprise enables whole-program optimization: the compiler emits the SDK external, then llvm-links the prebuilt sdk.ll with your IR and clang -O3s the merged module — cross-CU inlining and devirtualization across the SDK boundary. Best runtime performance, slower link time.
superj build --enterprise # dev + LTO
superj build --release --enterprise # release profile + LTO (max performance)
The build tool auto-detects the installed SDK variant from what's in $SJ_HOME/sdk/build/: libsuperj_sdk.a → community, sdk.ll → enterprise, neither → vip (--sdk-source). An explicit --enterprise overrides auto-detection. You normally don't need to think about it — superj build/run emit the correct link line for whichever variant you installed.
Build hooks (code generation)
Set buildScript = "tools/Gen.sj" to a standalone SuperJ program (with a main, living outside src/). Before each build it is compiled and run with the path to a generated-sources directory; every .sj it writes there is folded into the build. The hook is cached by its source hash. (This is the only place build code runs — the manifest itself is never executed.)
Feature flags
Conditionally compile optional source directories:
static final String[] features = { "json", "features/json", "tls", "features/tls" };
static final String[] defaultFeatures = { "json" };
An enabled feature's directory joins the build. Select with --features a,b, --no-default-features, or --all-features. (Granularity is whole source files, not in-file #[cfg].)
SDK linking
The standard library is an implicit, toolchain-pinned dependency. The build tool picks the SDK mode for you — the community archive by default, or --enterprise for whole-program LTO — and emits the correct, OS-specific link line. You never spell out clang or the runtime objects.