首页 · ← SuperJ 手册 中文|EN

构建可复用库包

SuperJ 做全程序编译:依赖的源码被编译进你的程序(没有按包的二进制 ABI)。一个"库"就是一个没有 entry 的项目 — 它导出其他项目 import 的类。本教程走一遍创建一个库、通过路径依赖从一个独立项目消费它,以及 git 依赖与工作区替代方案。

1. 搭建库

superj new mathlib
cd mathlib

脚手架生成的 Build.sj 有一个指向 mathlib.Mainentry 字段。一个库没有入口点,因此移除 entry(或设为 ""):

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;
}

删除脚手架生成的 src/mathlib/Main.sj(库不需要 main 类),然后写库:

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. 类型检查库

一个无 entry 的库没有可运行产物 — superj build 对它做类型检查但不链接。用 superj check 验证:

superj check
Checked mathlib (1 source files)

这是库的 CI 门:它编译源码并报告类型错误,但不产出二进制。库已可被消费。

3. 搭建消费者

在一个同级目录:

cd ..
superj new myapp
cd myapp

4. 把库作为路径依赖添加

superj add mathlib ../mathlib

这会编辑 Build.sj 加上依赖:

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;
}

你也可以手编 Build.sjdependencies 字段是一个 { "name", "path" } 对的扁平数组。

5. 使用库

编辑 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. 构建并运行

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

构建工具把库的源码与你的应用源码一起编译(全程序编译)并链接成一个二进制。要做 release 构建:superj run --release(应用清单的 releaseSimdreleaseBoundsCheckreleaseOpt 字段)。要最大性能:superj build --release --enterprise(全程序 LTO;见 构建系统)。

7. lockfile

首次构建后,消费者项目中出现一个 superj.lock 文件:

# superj.lock — 由 `superj build` 生成;提交此文件。
version = 1

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

提交 superj.lock — 它把依赖的内容哈希固定下来,用于可复现构建。如果库源码变化,哈希在下次构建时更新,superj.lock 反映新状态。

8. Git 依赖

对于托管在 git 仓库中的库(例如在 Gitea 服务器或本地裸仓库),用 --git 代替路径:

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

这会向 Build.sj 加一个 gitDependencies 字段:

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

构建工具在首次构建时把固定的提交拉取到内容寻址缓存($SUPERJ_CACHE_DIR,默认 ~/.superj/cache);热缓存可离线构建。lockfile 记录解析后的提交哈希:

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

用于测试的本地 git 仓库: 你可以用 file:// URL 对一个本地仓库测试 git 依赖流程。把库初始化为 git 仓库(git init && git add -A && git commit -m "init"),然后:superj add mathlib --git file:///path/to/mathlib --rev HEAD

9. 工作区(monorepo)

当库与消费者位于同一仓库时,用工作区代替路径依赖。创建一个带 workspaceMembers 的根 Build.sj

myrepo/
  Build.sj              ← 根清单(工作区)
  mathlib/
    Build.sj            ← 库(无 entry)
    src/mathlib/...
  myapp/
    Build.sj            ← 应用(entry = myapp.Main)
    src/myapp/...

Build.sj

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

消费者(myapp/Build.sj)仍把库声明为路径依赖(dependencies = { "mathlib", "../mathlib" })。从工作区根:

superj build -p myapp     # 只构建 myapp
superj check              # 类型检查所有成员
superj run -p myapp       # 构建 + 运行 myapp

从成员目录内部执行任何命令,它只作用于该成员,完全像一个独立项目。成员之间作为普通路径依赖相互依赖;一个成员(无 entry)只做类型检查而非链接。

10. 跨包命名

SuperJ 按全限定名为类符号建立命名空间,因此包不同但简单名相同的类可以共存 — com.a.Foocom.b.Foo,或 com.x.Config 与 SDK 的 sj.util.Config 并存。仍有两样东西会冲突,因为它们共享一个 mangle 后的符号:

编译器在 superj check 时报告这两种情况,指出违规的全限定名,而不是在链接时才失败。

sj. 包根是保留的,供 SDK 与编译器自身使用:用户代码声明 package sj;package sj.anything; 会在 check/build 时被以一个保留命名空间错误拒绝。这正是让上面的规则滴水不漏的原因 — 一个 SDK 类保持其历史符号名,没有用户类能进入那个命名空间与它冲突。你打包的类可以自由地引用 sj.* 类型(通过 import 或 FQN,照旧);它们只是不能在 sj.* 内被声明

这在所有构建模式下都成立:两个同简单名的类都从源码编译 — com.a.Foo + com.b.Foo,本地引用或全限定引用 — 在 community/enterprise(--sdk-path)与 VIP(--sdk-source)构建下都能共存并正确分派。

裸名如何解析。 一个裸 Foo 依次表示:当前包的 Foo;然后单类型 import 的 Fooimport com.a.Foo;);然后程序中名为 Foo 的那个类。如果两个或更多包都声明了 Foo 而上述规则都不命中,该引用是一个编译错误,列出每个候选(reference to 'Foo' is ambiguous: com.a.Foo, com.b.Foo — qualify it or add an import)— 绝不静默选择。一个其包不存在的限定名(new com.c.Foo()com.c 不存在)同样是 cannot find class 'com.c.Foo',而不是静默绑定别的 Foo

一个名为 Main 的库类,如果与消费者的 Main 同在默认包中,仍会冲突 — 按库类的功能命名MathLibStringUtilsHttpClient),并给它们一个包。

小结

步骤命令
搭建库superj new mylib → 从 Build.sj 移除 entry
类型检查库superj check
搭建消费者superj new myapp
添加路径依赖superj add mylib ../mylib
添加 git 依赖superj add mylib --git <url> --rev <rev>
使用库import mylib.MyClass;
构建 + 运行superj run
Release 构建superj build --release
最大性能superj build --release --enterprise
工作区构建superj build -p myapp(从工作区根)

完整的清单字段参考、feature flag 与构建钩子见 构建系统

SuperJ — manual · generated from libraries.md at pack time · Powered by superJ — this site is served by superj_web 中文|EN