Thanks for taking an interest in Sai. This page covers what you need to build the project and the few conventions that are not obvious from the code.
- JDK 21. The build declares a Java 21 toolchain, so Gradle will provision one if the JDK you launch it with is a different version.
- No local Gradle install. Use the wrapper:
./gradlew.
./gradlew build # compile, test, javadoc, JARs
./gradlew jar # just the engine JAR
./gradlew clean./gradlew compileJava alone does not give you a usable engine. The engine's
@ScriptClass types are rewritten after compilation by saigen, which also generates
their $Prototype and $Constructor companions. That step writes to
build/classes/saigen/main and only the JAR contains the result, so build jar (or
anything that depends on it) when you want to run something.
./gradlew test # Java tests + the nosecurity scripts
./gradlew testOptimistic # full script corpus, optimistic types on
./gradlew testPessimistic # full script corpus, optimistic types offThe three tasks share build/sai_code_cache and each clears it on the way in, so they
are ordered and never run concurrently. testOptimistic and testPessimistic are where
most of the coverage is - test runs only a small script subset.
To narrow a run, pass the selectors rather than editing build.gradle.kts:
./gradlew testOptimistic -Psai.test.roots=test/script/basic
./gradlew testOptimistic -Psai.test.includes='JDK-80*.js'
./gradlew testOptimistic -Psai.test.list=JDK-8006220.jsThe ECMAScript conformance suite is not part of the default build:
./gradlew downloadExternals # shallow-clones tc39/test262 (es5-tests)
./gradlew test262
./gradlew test262ParallelMost tests are JavaScript, not Java. A test is foo.js plus an optional
foo.js.EXPECTED; the harness runs the script and diffs stdout against the expected
output. Options come from a header comment:
/**
* @test
* @run
* @option -scripting
*/| Directory | Runs with |
|---|---|
test/script/basic |
file read on test/script, sai.test.* property read |
test/script/error |
scripts that must fail to compile; .EXPECTED holds the error |
test/script/sandbox |
no special permissions - asserts sandbox containment |
test/script/trusted |
AllPermission |
test/script/nosecurity |
run by test |
test/script/currently-failing |
excluded from every task - park known-broken tests here |
Tests run under the Turkish locale (-Duser.language=tr -Duser.country=TR) on purpose.
Locale-sensitive string operations without an explicit locale behave differently there,
so pin Locale.ROOT in any code you add.
src/main/java engine sources org.codelibs.sai.{api,internal,tools}
src/main/resources .properties, .js, META-INF/services
src/main/javadoc javadoc overview page
src/test/java Java tests
src/test/resources resources the Java tests load
test/script the JavaScript test corpus
buildtools/saigen the bytecode post-processor, built as its own source set
samples example scripts, used by `./gradlew run`
saigen compiles against the engine's own classes (it reads the annotations and emits
references to the runtime types) and then post-processes them, so it cannot move to
buildSrc or an included build.
- Keep the change focused, and say in the description what you verified.
- Run
./gradlew build testOptimistic testPessimisticbefore opening the PR. That is exactly what CI runs. - New behaviour needs a test. For engine changes that usually means a script test with
a
.EXPECTEDfile. - Do not reformat code you are not otherwise changing.
Sai is GPLv2 with the Classpath Exception, inherited from Nashorn. Keep the existing copyright headers intact - the licence requires it - and add your own only to files you create.