Home · ← SuperJ Manual EN|中文

The Build System

SuperJ ships a full 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

CommandWhat 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 checkType-check the whole project — no compile or link. Reports the same diagnostics build does, including the default-on escape warnings W_UNPROVEN_ESCAPE and W_ARENA_NO_SAVINGS for any program containing an arena block (#3868).
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 cleanRemove target/ — at a workspace root, every member's target/ too.
superj build --no-cache / superj test --no-cacheRebuild even when the stamp says up to date.
superj --helpList 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:

For a step-by-step tutorial on creating and consuming a library, see Building a Reusable Library.

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 links the prebuilt enterprise SDK with your program and optimizes the whole thing together — global 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/: an archive → community, a precompiled SDK module → enterprise, neither → vip. The SDK source tree (sdk/sj/) ships only in the VIP install, so --sdk-source is VIP-only — community/enterprise use --sdk-path (archive) or --sdk-path … --enterprise (whole-program LTO via sdk.ll). 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.

The merged whole-program link re-optimizes the entire SDK on every build — roughly 8 s per edit even for a 12-line program. Debug-profile superj build (and superj test) therefore fast-link instead: the SDK is precompiled to a native object once and cached (rebuilt only when the SDK changes), and each build just native-links your freshly compiled module against it — an edit→build cycle drops to ~0.25 s. This is the same link shape as the community archive, so behavior is identical; only cross-SDK- boundary inlining is deferred to release builds. superj build --release always uses the full merged LTO link. On the superj compile CLI, pass --enterprise --fast-link explicitly.

Native code (nativeSources)

A package can implement some of its methods in C. Declare each method native (no body), and list the C that implements it in Build.sj:

// Build.sj
static final String[] capabilities  = { "app", "ffi,console" };
static final String[] nativeSources = { "native/add.c" };
// src/app/Main.sj
package app;

public class Main {
    public native static long add(long a, long b);
    public static void main(String[] args) { System.out.println(add(40, 2)); }
}
// native/add.c -- the symbol is the mangled name from SPEC §9.5.1
#include <stdint.h>
int64_t sj_app_Main_add__JJ(int64_t a, int64_t b) { return a + b; }

superj build, run and test compile the listed files with the toolchain's clang and link them into the program and into every test suite. A library member's native sources are linked into each binary that depends on it. Editing a .c file relinks on the next build.

Entries are paths relative to the package root that stay inside it and name existing .c files. Declaring capabilities means the package also needs the ffi grant, because a native can call anything in libc (see claims). For a single file outside a project, superj compile --link --link-extra native/add.c does the same (demo/sj/demo/NativeDemo.sj).

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 the linker command. --sdk-source is VIP-only (community/enterprise don't ship sdk/sj/); the build tool picks the right path automatically.

SuperJ — manual · generated from build-system.md at pack time EN|中文