Skip to content

Commit 9c6b6d1

Browse files
Random-ScientistMCRcortexOcelot5836
authored
declarative natives build script, MSVC for Windows targets + FreeBSD x86 & Windows ARM natives (#231)
the MSVC targets are a much better option than gnullvm for windows ARM (tier 3 toolchain vs tier 1). not sure if there's a nice approach to sticking everything we need in one container but with caching the duplication isnt too bad. also not sure if the dev commands should be changed to call local cargo directly, since anyone working on the natives will probably be doing that anyway. * *actually* ensures non-`x86_64-apple-darwin` release builds get `codegen-units = 1`, after moving to a workspace, since workspaces ignore profile overrides in member crates' Cargo.toml. * native build script now declaratively generates tasks from a single list of targets, which also handles copying/etc as opposed to the previous ad-hoc handwritten tasks * the natives are now LZMA compressed and decompressed at load time * should fix #40, though loader improvements may be needed to detect freebsd and avoid loading the linux natives. * fixes #31 * might fix #206 (by changing how the natives file is named) --------- Co-authored-by: Random-Scientist <Random-Scientist@users.noreply.github.com> Co-authored-by: mcrcortex <18544518+MCRcortex@users.noreply.github.com> Co-authored-by: Ocelot <FinntheRaider@gmail.com>
1 parent 0b6ddf5 commit 9c6b6d1

17 files changed

Lines changed: 169 additions & 157 deletions

File tree

.github/workflows/build-rust.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ jobs:
3737
key: ${{ runner.os }}-node-${{ hashFiles('common/src/main/rust/**/Cargo.lock') }}
3838

3939
- name: Run Gradle Task
40-
run: ./gradlew common:buildImage common:buildRustNatives
40+
run: ./gradlew common:buildImages common:buildRustNatives
4141

4242
- name: Take ownership of the workspace
4343
run: sudo chown -R $USER:$USER .

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ View the [Sable Developer Wiki](https://github.com/ryanhcode/sable/wiki) for doc
2727
# Building Rust Natives
2828

2929
1. Install Docker from https://www.docker.com/get-started/ or from your relevant package manager
30-
2. Run `gradlew common:buildImage` (only has to be done once)
30+
2. Run `gradlew common:buildImages` (only has to be done once)
3131
3. Run `gradlew common:buildRustNatives`
3232

3333
### Thanks

common/build.gradle

Lines changed: 104 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,53 @@
11
import org.apache.tools.ant.taskdefs.condition.Os
22

3+
import org.tukaani.xz.LZMA2Options
4+
import org.tukaani.xz.XZOutputStream
5+
6+
import java.nio.file.Files
7+
buildscript {
8+
dependencies {
9+
classpath 'org.tukaani:xz:1.10'
10+
}
11+
}
12+
313
plugins {
414
id 'multiloader-common'
515
id 'net.neoforged.moddev'
616
}
717

18+
def mac(arch) {
19+
[triple: "${arch}-apple-darwin", arch: arch, os: "macos", ext: "dylib", lib: true]
20+
}
21+
def linux(arch) {
22+
[triple: "${arch}-unknown-linux-gnu", arch: arch, os: "linux", ext: "so", lib: true]
23+
}
24+
def freebsd(arch) {
25+
[triple: "${arch}-unknown-freebsd", arch: arch, os: "freebsd", ext: ".so", lib: true]
26+
}
27+
def windows(arch) {
28+
[triple: "${arch}-pc-windows-msvc", arch: arch, os: "windows", ext: "dll", lib: false]
29+
}
30+
31+
def supportedTargets = [
32+
mac("x86_64"),
33+
mac("aarch64"),
34+
linux("x86_64"),
35+
linux("aarch64"),
36+
windows("x86_64"),
37+
windows("aarch64"),
38+
freebsd("x86_64"),
39+
// freebsd("aarch64"),
40+
]
41+
def pinnedRustVersion = 'nightly-2026-01-29'
842
def rustRootDir = file("src/main/rust")
943
def rustProjectDir = file("$rustRootDir/rapier")
1044
def nativesDir = file("src/main/resources/natives/sable_rapier")
1145
def cargoCacheDir = project.layout.buildDirectory.file("cargo").get().asFile
1246
def docker = Os.isFamily(Os.FAMILY_MAC) ? "/usr/local/bin/docker" : "docker"
13-
def dockerCommand = [
47+
def xWinImage = 'sable-build-xwin'
48+
def zigbuildImage = 'sable-build-zigbuild'
49+
50+
def baseDockerCommand = [
1451
docker,
1552
'run',
1653
'--rm',
@@ -22,8 +59,9 @@ def dockerCommand = [
2259
"$cargoCacheDir/registry:/usr/local/cargo/registry",
2360
'-w',
2461
'/io/rapier',
25-
'sable-build'
2662
]
63+
project.ext.zigbuildCargo = [baseDockerCommand, zigbuildImage, 'cargo'].flatten()
64+
project.ext.xWinCargo = [baseDockerCommand, xWinImage, 'cargo'].flatten()
2765

2866
tasks.register('createContainersDirectory') {
2967
doLast {
@@ -33,116 +71,112 @@ tasks.register('createContainersDirectory') {
3371
}
3472
}
3573

36-
tasks.register('buildImage', Exec) {
74+
75+
tasks.register('buildZigbuildImage', Exec) {
3776
group = 'rust'
3877
workingDir rustRootDir
39-
commandLine docker, 'build', '-t', 'sable-build', '.'
78+
commandLine docker, 'build', '-t', zigbuildImage, 'container/zigbuild', '--build-arg', "RUST_VERSION=${pinnedRustVersion}"
4079
dependsOn createContainersDirectory
4180
}
42-
43-
tasks.register('cleanRust', Exec) {
81+
tasks.register('buildXWinImage', Exec) {
4482
group = 'rust'
45-
workingDir rustProjectDir
46-
commandLine dockerCommand
47-
args 'cargo', 'clean'
83+
workingDir rustRootDir
84+
commandLine docker, 'build', '-t', xWinImage, 'container/xwin', '--build-arg', "RUST_VERSION=${pinnedRustVersion}"
4885
dependsOn createContainersDirectory
4986
}
5087

51-
tasks.register('compileRust', Exec) {
88+
tasks.register('buildImages') {
5289
group = 'rust'
53-
workingDir rustProjectDir
54-
commandLine dockerCommand
55-
args 'cargo', 'zigbuild'
56-
dependsOn createContainersDirectory
57-
finalizedBy copyRustNativesDev
90+
dependsOn buildZigbuildImage, buildXWinImage
5891
}
5992

60-
tasks.register('compileRustMacAArch64', Exec) {
93+
tasks.register('cleanRust', Exec) {
6194
group = 'rust'
6295
workingDir rustProjectDir
63-
commandLine dockerCommand
64-
args 'cargo', 'zigbuild', '--release', '--target', 'aarch64-apple-darwin'
96+
commandLine project.ext.zigbuildCargo // only need to use zigbuild cargo since both containers share the same target folder
97+
args 'clean'
6598
dependsOn createContainersDirectory
6699
}
67100

68-
tasks.register('compileRustMacX86', Exec) {
69-
group = 'rust'
70-
workingDir rustProjectDir
71-
commandLine dockerCommand
72-
args 'cargo', 'zigbuild', '--release', '--target', 'x86_64-apple-darwin'
73-
dependsOn createContainersDirectory
101+
def compileRustTaskName = { target ->
102+
"compileRust-${target.os}-${target.arch}"
74103
}
75104

76-
tasks.register('compileRustLinuxAArch64', Exec) {
77-
group = 'rust'
78-
workingDir rustProjectDir
79-
commandLine dockerCommand
80-
args 'cargo', 'zigbuild', '--release', '--target', 'aarch64-unknown-linux-gnu.2.17'
81-
dependsOn createContainersDirectory
82-
}
105+
def commandLineForTarget(target) {
106+
if(target.triple.contains('msvc')) {
107+
[project.ext.xWinCargo, 'xwin', 'build', '--release', '--target', target.triple].flatten()
108+
// support meme platforms, this does nothing by default because the target is disabled
109+
} else if(target.triple == 'aarch64-unknown-freebsd') {
110+
[project.ext.zigbuildCargo, 'zigbuild', '-Z', 'build-std', '--release', '--target', target.triple].flatten()
111+
} else {
112+
[project.ext.zigbuildCargo, 'zigbuild', '--release', '--target', target.triple].flatten()
113+
}
83114

84-
tasks.register('compileRustLinux', Exec) {
85-
group = 'rust'
86-
workingDir rustProjectDir
87-
commandLine dockerCommand
88-
args 'cargo', 'zigbuild', '--release', '--target', 'x86_64-unknown-linux-gnu.2.17'
89-
dependsOn createContainersDirectory
90115
}
91-
92-
tasks.register('compileRustWindows', Exec) {
93-
group = 'rust'
94-
workingDir rustProjectDir
95-
commandLine dockerCommand
96-
args 'cargo', 'zigbuild', '--release', '--target', 'x86_64-pc-windows-gnu'
97-
dependsOn createContainersDirectory
116+
def nativesNameForTarget(target) {
117+
"sable_rapier_${target.arch}_${target.os}.${target.ext}"
118+
}
119+
supportedTargets.forEach { target ->
120+
tasks.register(compileRustTaskName(target), Exec) {
121+
group = 'rust'
122+
workingDir rustProjectDir
123+
description = "Cross-compiles natives for the ${target.triple} target"
124+
commandLine = commandLineForTarget(target)
125+
}
98126
}
99127

100128
tasks.register('buildRustNatives') {
101129
group = 'build'
102130
description = 'Compiles all Rust natives and moves them to resources.'
103131

104-
dependsOn compileRustMacAArch64, compileRustMacX86, compileRustLinux, compileRustLinuxAArch64, compileRustWindows
132+
dependsOn = supportedTargets.stream().map(compileRustTaskName).collect()
105133
finalizedBy copyRustNatives
106134
}
107135

108136
tasks.register('copyRustNatives', Copy) {
109137
group = 'rust'
110-
111138
into nativesDir
112139
mustRunAfter(buildRustNatives)
113140

114-
def moves = [
115-
[src: "target/aarch64-apple-darwin/release/libsable_rapier.dylib", dest: "sable_rapier_aarch64_macos.dylib"],
116-
[src: "target/x86_64-apple-darwin/release/libsable_rapier.dylib", dest: "sable_rapier_x86_64_macos.dylib"],
117-
[src: "target/x86_64-unknown-linux-gnu/release/libsable_rapier.so", dest: "sable_rapier_x86_64_linux.so"],
118-
[src: "target/aarch64-unknown-linux-gnu/release/libsable_rapier.so", dest: "sable_rapier_aarch64_linux.so"],
119-
[src: "target/x86_64-pc-windows-gnu/release/sable_rapier.dll", dest: "sable_rapier_x86_64_windows.dll"]
120-
]
121-
122-
moves.forEach { map ->
123-
from(file("$rustRootDir/${map.src}")) {
124-
rename { map.dest }
141+
supportedTargets.forEach { target ->
142+
from(file("$rustRootDir/target/${target.triple}/release/${ if(target.lib) {"lib"} else {""}}sable_rapier.${target.ext}")) {
143+
rename { nativesNameForTarget(target) }
125144
}
126145
}
146+
finalizedBy packRustNatives
127147
}
128148

129149
tasks.register('copyRustNativesDev', Copy) {
130150
group = 'rust'
151+
into nativesDir
131152

153+
supportedTargets.forEach { target ->
154+
from(file("$rustRootDir/target/debug/${ if(target.lib) {"lib"} else {""}}sable_rapier.${target.ext}")) {
155+
rename { nativesNameForTarget(target) }
156+
}
157+
}
158+
finalizedBy packRustNatives
159+
}
160+
tasks.register('packRustNatives', Tar) {
161+
group = 'rust'
162+
archiveFile.set file("${nativesDir}/sable_rapier_binaries.tar.xz")
132163
into nativesDir
133-
mustRunAfter(compileRust)
134-
135-
def moves = [
136-
[src: "target/debug/libsable_rapier.dylib", dest: "sable_rapier_aarch64_macos.dylib"],
137-
[src: "target/debug/libsable_rapier.dylib", dest: "sable_rapier_x86_64_macos.dylib"],
138-
[src: "target/debug/libsable_rapier.so", dest: "sable_rapier_aarch64_linux.so"],
139-
[src: "target/debug/libsable_rapier.so", dest: "sable_rapier_x86_64_linux.so"],
140-
[src: "target/debug/sable_rapier.dll", dest: "sable_rapier_aarch64_windows.dll"],
141-
[src: "target/debug/sable_rapier.dll", dest: "sable_rapier_x86_64_windows.dll"]
142-
]
143-
moves.forEach { map ->
144-
from(file("$rustRootDir/${map.src}")) {
145-
rename { map.dest }
164+
165+
mustRunAfter copyRustNatives, copyRustNativesDev
166+
supportedTargets.forEach { target ->
167+
var f = nativesNameForTarget(target);
168+
// No, you can't use rename here in case you were wondering.
169+
from(file("${nativesDir}/${f}")) { eachFile { setPath f } }
170+
}
171+
doLast {
172+
byte[] bytes = Files.readAllBytes(getArchiveFile().get().asFile.toPath());
173+
try (var f = new FileOutputStream(getArchiveFile().get().asFile)) {
174+
try (var x = new XZOutputStream(f, new LZMA2Options(LZMA2Options.PRESET_MAX))) {
175+
x.write(bytes);
176+
}
177+
}
178+
supportedTargets.forEach { t ->
179+
delete(file("${nativesDir}/${nativesNameForTarget(t)}"))
146180
}
147181
}
148182
}

common/src/main/java/dev/ryanhcode/sable/physics/impl/rapier/Rapier3D.java

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,13 @@
99
import net.minecraft.Util;
1010
import net.minecraft.Util.OS;
1111
import net.minecraft.server.level.ServerLevel;
12+
13+
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
14+
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
1215
import org.jetbrains.annotations.ApiStatus;
1316
import org.joml.Matrix3dc;
1417
import org.joml.Vector3dc;
18+
import org.tukaani.xz.XZInputStream;
1519

1620
import java.io.FileNotFoundException;
1721
import java.io.InputStream;
@@ -57,19 +61,33 @@ private static String getNativeName() {
5761

5862
private static void loadLibrary() {
5963
final String nativeName = getNativeName();
60-
try (final InputStream is = Rapier3D.class.getResourceAsStream("/natives/" + LIB_NAME + "/" + nativeName)) {
64+
try (final InputStream is = Rapier3D.class.getResourceAsStream("/natives/" + LIB_NAME + "/sable_rapier_binaries.tar.xz")) {
6165
if (is == null) {
62-
throw new FileNotFoundException(LIB_NAME);
66+
throw new FileNotFoundException("sable_rapier_binaries.tar.xz");
67+
}
68+
try (final XZInputStream is2 = new XZInputStream(is);
69+
final TarArchiveInputStream ti = new TarArchiveInputStream(is2)) {
70+
71+
TarArchiveEntry entry;
72+
while ((entry = ti.getNextEntry()) != null) {
73+
if (entry.getName().equals(nativeName)) {
74+
final String[] split = nativeName.split("\\.");
75+
final Path tempFile = Files.createTempFile(split[0], "." + split[1]);
76+
Files.copy(ti, tempFile, StandardCopyOption.REPLACE_EXISTING);
77+
System.load(tempFile.toAbsolutePath().toString());
78+
ENABLED = true;
79+
return;
80+
}
81+
}
82+
83+
throw new FileNotFoundException(nativeName);
6384
}
64-
65-
final Path tempFile = Files.createTempFile(LIB_TMP_DIR_PREFIX, null);
66-
Files.copy(is, tempFile, StandardCopyOption.REPLACE_EXISTING);
67-
System.load(tempFile.toAbsolutePath().toString());
68-
ENABLED = true;
6985
} catch (final Throwable t) {
7086
ENABLED = false;
7187

72-
Sable.LOGGER.error("Sable has failed to load the natives needed for its Rapier pipeline. Native library name {}. Please report with system details and logs to {}", nativeName, Sable.ISSUE_TRACKER_URL, t);
88+
Sable.LOGGER.error(
89+
"Sable has failed to load the natives needed for its Rapier pipeline. Native library name {}. Please report with system details and logs to {}",
90+
nativeName, Sable.ISSUE_TRACKER_URL, t);
7391
}
7492
}
7593

Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

common/src/main/rust/Cargo.toml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,11 @@ jni = "0.21.1"
1414
colored = "2.1.0"
1515
fern = { version = "0.6.2", features = ["colored"] }
1616
humantime = "2.1.0"
17+
18+
19+
[profile.release]
20+
lto = "thin"
21+
codegen-units = 1
22+
23+
[profile.bench]
24+
debug = true

0 commit comments

Comments
 (0)