Home · ← SuperJ Manual EN|中文

Building a Reusable Library Package

SuperJ compiles whole-program: a dependency's source is compiled into your program (there is no per-package binary ABI). A "library" is simply a project with no entry — it exports classes that other projects import. This tutorial walks through creating a library, consuming it from a separate project via a path dependency, and the git-dependency and workspace alternatives.

1. Scaffold the library

superj new mathlib
cd mathlib

The scaffolded Build.sj has an entry field pointing at mathlib.Main. A library has no entry point, so remove the entry line (or set it to ""):

public class Build {
    static final String   name    = "mathlib";
    static final String   version = "0.1.0";
    static final String   spec    = "1.0";
    static final String[] dependencies = {};
    static final String   releaseSimd        = "sse2";
    static final boolean  releaseBoundsCheck = true;
    static final int      releaseOpt         = 3;
}

Delete the scaffolded src/mathlib/Main.sj (a library doesn't need a main class) and write the library:

rm src/mathlib/Main.sj
// src/mathlib/MathLib.sj
package mathlib;

public class MathLib {
    public static int square(int x) {
        return x * x;
    }

    public static int factorial(int n) {
        int result = 1;
        for (int i = 2; i <= n; i = i + 1) {
            result = result * i;
        }
        return result;
    }

    public static boolean isPrime(int n) {
        if (n < 2) { return false; }
        if (n < 4) { return true; }
        if (n % 2 == 0) { return false; }
        for (int i = 3; i * i <= n; i = i + 2) {
            if (n % i == 0) { return false; }
        }
        return true;
    }
}

2. Type-check the library

A library with no entry has no runnable artifact — superj build type-checks it but doesn't link. Use superj check to verify:

superj check
Checked mathlib (1 source files)

This is the library's CI gate: it compiles the sources and reports type errors, but produces no binary. The library is ready to consume.

3. Scaffold the consumer

In a sibling directory:

cd ..
superj new myapp
cd myapp

4. Add the library as a path dependency

superj add mathlib ../mathlib

This edits Build.sj to add the dependency:

public class Build {
    static final String   name    = "myapp";
    static final String   version = "0.1.0";
    static final String   spec    = "1.0";
    static final String   entry   = "myapp.Main";
    static final String[] dependencies = { "mathlib", "../mathlib" };
    static final String   releaseSimd        = "sse2";
    static final boolean  releaseBoundsCheck = true;
    static final int      releaseOpt         = 3;
}

You can also edit Build.sj by hand — the dependencies field is a flat array of { "name", "path" } pairs.

5. Use the library

Edit src/myapp/Main.sj:

// src/myapp/Main.sj
package myapp;

import mathlib.MathLib;

public class Main {
    public static void main(String[] args) {
        System.out.println("square(7) = " + MathLib.square(7));
        System.out.println("factorial(5) = " + MathLib.factorial(5));
        System.out.println("isPrime(17) = " + MathLib.isPrime(17));
    }
}

6. Build and run

superj run
Built /path/to/myapp/target/debug/myapp
square(7) = 49
factorial(5) = 120
isPrime(17) = true

The build tool compiles the library's sources together with your app's sources (whole-program compilation) and links a single binary. For a release build: superj run --release (applies the manifest's releaseSimd, releaseBoundsCheck, releaseOpt fields). For maximum performance: superj build --release --enterprise (whole-program LTO; see The Build System).

7. The lockfile

After the first build, a superj.lock file appears in the consumer project:

# superj.lock — generated by `superj build`; commit this file.
version = 1

[[package]]
name = "mathlib"
path = "../mathlib"
hash = "c989d08c7e7c3989b7687ace7847e3339b82e0b84f9b4907deb95d0d7fe929f9"

Commit superj.lock — it pins the dependency's content hash for reproducible builds. If the library source changes, the hash updates on the next build and superj.lock reflects the new state.

8. Git dependencies

For a library hosted in a git repository (e.g. on a Gitea server or a local bare repo), use --git instead of a path:

superj add mathlib --git git@example.com:me/mathlib --rev v1.0.0

This adds a gitDependencies field to Build.sj:

static final String[] gitDependencies = { "mathlib", "git@example.com:me/mathlib", "v1.0.0" };

The build tool fetches the pinned commit into a content-addressed cache ($SUPERJ_CACHE_DIR, default ~/.superj/cache) on first build; a warm cache builds offline. The lockfile records the resolved commit hash:

[[package]]
name = "mathlib"
source = "git"
url = "git@example.com:me/mathlib"
commit = "a1b2c3d4e5f6..."
hash = "..."

Local git repos for testing: you can use file:// URLs to test the git-dependency flow against a local repo. Initialize the library as a git repo (git init && git add -A && git commit -m "init"), then: superj add mathlib --git file:///path/to/mathlib --rev HEAD.

9. Workspaces (monorepo)

When the library and consumer live in the same repository, use a workspace instead of a path dependency. Create a root Build.sj with workspaceMembers:

myrepo/
  Build.sj              ← root manifest (workspace)
  mathlib/
    Build.sj            ← library (no entry)
    src/mathlib/...
  myapp/
    Build.sj            ← app (entry = myapp.Main)
    src/myapp/...

Root Build.sj:

public class Build {
    static final String[] workspaceMembers = { "mathlib", "myapp" };
}

The consumer (myapp/Build.sj) still declares the library as a path dependency (dependencies = { "mathlib", "../mathlib" }). From the workspace root:

superj build -p myapp     # build just myapp
superj check              # type-check all members
superj run -p myapp       # build + run myapp

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; a member that is a library (no entry) is type-checked rather than linked.

10. Naming across packages

SuperJ namespaces class symbols by fully-qualified name, so package-distinct classes with the same simple name coexist — com.a.Foo and com.b.Foo, or a com.x.Config alongside the SDK's sj.util.Config. Two things still collide, because they share a mangled symbol:

The compiler reports both cases at superj check naming the offending fully-qualified names, rather than failing later at link.

The sj. package root is reserved for the SDK and the compiler itself: user code declaring package sj; or package sj.anything; is rejected at check/build with a reserved namespace error. This is what makes the rule above airtight — an SDK class keeps its historic symbol name, and no user class can enter that namespace and collide with it. Your packaged classes may freely reference sj.* types (by import or FQN, as always); they just can't be declared inside sj.*.

This holds across all build modes: two same-simple-name classes both compiled from source — com.a.Foo + com.b.Foo, referenced locally or fully qualified — coexist and dispatch correctly under both the community/enterprise (--sdk-path) and VIP (--sdk-source) builds.

How a bare name resolves. A bare Foo means, in order: the current package's Foo; then a single-type-imported Foo (import com.a.Foo;); then the one class named Foo in the program. If two or more packages declare Foo and neither rule above picks one, the reference is a compile error naming every candidate (reference to 'Foo' is ambiguous: com.a.Foo, com.b.Foo — qualify it or add an import) — never a silent pick. A qualified name whose package doesn't exist (new com.c.Foo() with no com.c) is likewise cannot find class 'com.c.Foo' instead of silently binding some other Foo.

A library class named Main still collides with the consumer's Main if both are in the default package — name library classes after what they do (MathLib, StringUtils, HttpClient), and give them a package.

Summary

StepCommand
Scaffold a librarysuperj new mylib → remove entry from Build.sj
Type-check the librarysuperj check
Scaffold a consumersuperj new myapp
Add a path depsuperj add mylib ../mylib
Add a git depsuperj add mylib --git <url> --rev <rev>
Use the libraryimport mylib.MyClass;
Build + runsuperj run
Release buildsuperj build --release
Max performancesuperj build --release --enterprise
Workspace buildsuperj build -p myapp (from workspace root)

See The Build System for the full manifest field reference, feature flags, and build hooks.

SuperJ — manual · generated from libraries.md at pack time EN|中文