Compare commits

...
Author SHA1 Message Date
EIREXE 0a6677ae5d Add libxi
CI / production-build (pull_request) Failing after 2m38s
CI / build-and-test (pull_request) Successful in 1m26s
2026-09-24 16:18:31 +02:00
EIREXE f05926e1be Install Blender XFixes runtime dependency
CI / production-build (pull_request) Failing after 2m13s
CI / build-and-test (pull_request) Successful in 1m28s
2026-09-24 16:09:01 +02:00
EIREXE ac7c57e288 Pin Blender for CI asset processing
CI / build-and-test (pull_request) Successful in 1m27s
CI / production-build (pull_request) Failing after 3m8s
2026-09-24 13:14:02 +02:00
4 changed files with 93 additions and 6 deletions
+32 -2
View File
@@ -63,7 +63,6 @@ jobs:
libxkbcommon-dev \ libxkbcommon-dev \
libxkbcommon-x11-0 \ libxkbcommon-x11-0 \
mesa-vulkan-drivers \ mesa-vulkan-drivers \
blender \
mold \ mold \
pkg-config pkg-config
@@ -130,22 +129,53 @@ jobs:
libusb-1.0-0-dev \ libusb-1.0-0-dev \
libwayland-dev \ libwayland-dev \
libx11-dev \ libx11-dev \
libxfixes3 \
libxi6 \
libxkbcommon-dev \ libxkbcommon-dev \
libxkbcommon-x11-0 \ libxkbcommon-x11-0 \
lld-19 \ lld-19 \
llvm-19 \ llvm-19 \
mesa-vulkan-drivers \ mesa-vulkan-drivers \
mold \ mold \
blender \
pkg-config pkg-config
sudo update-alternatives --install /usr/bin/clang clang /usr/bin/clang-19 100 sudo update-alternatives --install /usr/bin/clang clang /usr/bin/clang-19 100
sudo update-alternatives --install /usr/bin/clang++ clang++ /usr/bin/clang++-19 100 sudo update-alternatives --install /usr/bin/clang++ clang++ /usr/bin/clang++-19 100
echo "/usr/lib/llvm-19/bin" >> "$GITHUB_PATH" echo "/usr/lib/llvm-19/bin" >> "$GITHUB_PATH"
rm -f "$HOME/.cache/cargo-xwin/clang-cl" rm -f "$HOME/.cache/cargo-xwin/clang-cl"
- name: Install pinned Blender
run: |
scripts/install-blender.sh "$RUNNER_TEMP/blender"
echo "BLENDER=$RUNNER_TEMP/blender/blender" >> "$GITHUB_ENV"
- name: Preprocess assets - name: Preprocess assets
run: cargo run --locked --bin process-assets --no-default-features --features debug_features -j 4 run: cargo run --locked --bin process-assets --no-default-features --features debug_features -j 4
- name: Verify processed battlefield asset
run: |
python3 - <<'PY'
import json
import struct
path = "imported_assets/Default/stages/battlefield/bf2.blend"
with open(path, "rb") as asset:
header = asset.read(12)
if len(header) != 12:
raise SystemExit(f"processed asset is truncated: {path}")
magic, version, length = struct.unpack("<4sII", header)
if magic != b"glTF" or version != 2 or length != asset.seek(0, 2):
raise SystemExit(f"processed asset is not a valid GLB: {path}")
asset.seek(12)
json_length, chunk_type = struct.unpack("<II", asset.read(8))
if chunk_type != 0x4E4F534A:
raise SystemExit(f"processed asset has no JSON chunk: {path}")
document = json.loads(asset.read(json_length))
meshes = document.get("meshes", [])
if not meshes:
raise SystemExit(f"processed asset contains no meshes: {path}")
print(f"{path}: {len(meshes)} meshes, {length} bytes")
PY
- name: Build production binary - name: Build production binary
run: cargo build --locked --profile production --no-default-features --features debug_features -j 4 run: cargo build --locked --profile production --no-default-features --features debug_features -j 4
+30
View File
@@ -0,0 +1,30 @@
#!/usr/bin/env bash
set -euo pipefail
version="5.2.2"
sha256="84098912789dc450e95697c4184fb8a90acbe5111c2ba4aede3fecb57806a168"
install_dir="${1:-${BLENDER_INSTALL_DIR:-${HOME}/.local/blender-${version}}}"
if [[ "$(uname -s)" != Linux || "$(uname -m)" != x86_64 ]]; then
echo "error: this installer currently supports Linux x86_64 only" >&2
exit 1
fi
if [[ -x "$install_dir/blender" ]]; then
"$install_dir/blender" --version | head -n 1
exit 0
fi
mkdir -p "$(dirname "$install_dir")"
tmp_dir="$(mktemp -d)"
trap 'rm -rf "$tmp_dir"' EXIT
archive="blender-${version}-linux-x64.tar.xz"
url="https://download.blender.org/release/Blender5.2/${archive}"
curl --fail --location --silent --show-error --output "$tmp_dir/$archive" "$url"
printf '%s %s\n' "$sha256" "$tmp_dir/$archive" | sha256sum --check --status
tar --extract --xz --file "$tmp_dir/$archive" --directory "$tmp_dir"
mv "$tmp_dir/blender-${version}-linux-x64" "$install_dir"
"$install_dir/blender" --version | head -n 1
+22 -4
View File
@@ -84,10 +84,11 @@ impl AssetLoader for BlendSourceLoader {
.arg(&blend_path) .arg(&blend_path)
.arg("--python") .arg("--python")
.arg(&script) .arg(&script)
.arg("--") .arg("--")
.arg(&output) .arg(&output)
.arg(&actions_path) .arg(&actions_path)
.output() .arg(&blend_path)
.output()
.map_err(|error| io::Error::other(format!("failed to start Blender: {error}")))?; .map_err(|error| io::Error::other(format!("failed to start Blender: {error}")))?;
if !result.status.success() { if !result.status.success() {
return Err(io::Error::other(format!( return Err(io::Error::other(format!(
@@ -106,6 +107,7 @@ impl AssetLoader for BlendSourceLoader {
let mut dependencies = manifest.dependencies; let mut dependencies = manifest.dependencies;
let document: serde_json::Value = serde_json::from_slice(&fs::read(&output)?) let document: serde_json::Value = serde_json::from_slice(&fs::read(&output)?)
.map_err(|error| io::Error::new(io::ErrorKind::InvalidData, error))?; .map_err(|error| io::Error::new(io::ErrorKind::InvalidData, error))?;
validate_export(&document, &output)?;
if let Some(images) = document.get("images").and_then(serde_json::Value::as_array) { if let Some(images) = document.get("images").and_then(serde_json::Value::as_array) {
for image in images { for image in images {
if let Some(uri) = image.get("uri").and_then(serde_json::Value::as_str) { if let Some(uri) = image.get("uri").and_then(serde_json::Value::as_str) {
@@ -142,6 +144,22 @@ impl AssetLoader for BlendSourceLoader {
} }
} }
fn validate_export(document: &serde_json::Value, output: &Path) -> io::Result<()> {
for key in ["scenes", "nodes", "meshes"] {
let count = document
.get(key)
.and_then(serde_json::Value::as_array)
.map_or(0, Vec::len);
if count == 0 {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("Blender export {} contains no {key}", output.display()),
));
}
}
Ok(())
}
async fn referenced_actions( async fn referenced_actions(
load_context: &mut LoadContext<'_>, load_context: &mut LoadContext<'_>,
root: &Path, root: &Path,
+9
View File
@@ -7,6 +7,15 @@ import sys
def main(): def main():
output = sys.argv[sys.argv.index("--") + 1] output = sys.argv[sys.argv.index("--") + 1]
actions_path = sys.argv[sys.argv.index("--") + 2] actions_path = sys.argv[sys.argv.index("--") + 2]
source_path = sys.argv[sys.argv.index("--") + 3]
loaded_path = os.path.realpath(bpy.data.filepath)
expected_path = os.path.realpath(source_path)
if loaded_path != expected_path:
raise RuntimeError(
f"Blender loaded {loaded_path!r} instead of {expected_path!r}"
)
if not bpy.data.objects or not bpy.data.meshes:
raise RuntimeError("Blender loaded an empty scene")
with open(actions_path) as file: with open(actions_path) as file:
actions_to_keep = set(json.load(file)) actions_to_keep = set(json.load(file))