feat: dreport-service + dreport-ffi + nuget packages
Some checks failed
CI / rust (push) Failing after 40s
CI / frontend (push) Failing after 1m53s
CI / wasm (push) Successful in 1m45s
CI / publish-crates (push) Has been skipped
CI / publish-npm (push) Has been skipped

Extract orchestration (font registry + render pipeline) from the Axum
backend into a standalone dreport-service crate. Backend becomes a thin
HTTP adapter on top.

Add dreport-ffi (cdylib) exposing the service through a stable C ABI
with opaque handles, byte buffers, and thread-local error reporting.

Build Dreport.Service + Dreport.AspNetCore NuGet packages under
bindings/dotnet/, packing the host RID native binary via a generated
nuspec. justfile recipes (nuget-publish, nuget-publish-all) build,
pack, and push to the Gitea NuGet registry in one shot.

Test coverage: 47 Rust + 38 C# (xUnit + WebApplicationFactory).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-28 16:19:47 +03:00
parent 92583141c9
commit 2db5929e39
44 changed files with 3377 additions and 252 deletions

171
justfile
View File

@@ -113,3 +113,174 @@ publish-layout:
publish-all:
just publish-core
just publish-layout
# --- NuGet (Dreport.Service) ---
# Gitea NuGet feed (override env var DREPORT_NUGET_TOKEN if rotated).
NUGET_REGISTRY_URL := "https://gitea.duhanbalci.com/api/packages/duhanbalci/nuget/index.json"
NUGET_TOKEN := env_var_or_default("DREPORT_NUGET_TOKEN", "56b178d79d9cf9dea1c4b90d836d55e41ddff897")
NUGET_VERSION := "0.2.0"
# Build dreport-ffi for the host RID and copy the dylib into runtimes/.
nuget-build-native-host:
#!/usr/bin/env bash
set -euo pipefail
cargo build --release -p dreport-ffi
case "$(uname -s)-$(uname -m)" in
Darwin-arm64) RID=osx-arm64; PREFIX=lib; EXT=dylib ;;
Darwin-x86_64) RID=osx-x64; PREFIX=lib; EXT=dylib ;;
Linux-x86_64) RID=linux-x64; PREFIX=lib; EXT=so ;;
Linux-aarch64) RID=linux-arm64; PREFIX=lib; EXT=so ;;
*) echo "unsupported host: $(uname -s)-$(uname -m)" >&2; exit 1 ;;
esac
DEST="bindings/dotnet/src/Dreport.Service/runtimes/$RID/native"
mkdir -p "$DEST"
cp "target/release/${PREFIX}dreport_ffi.$EXT" "$DEST/${PREFIX}dreport_ffi.$EXT"
# Cross-compile dreport-ffi for all supported RIDs into runtimes/.
# Requires: rustup targets installed + cargo-zigbuild (`cargo install cargo-zigbuild` and `brew install zig`).
nuget-build-native-all:
#!/usr/bin/env bash
set -euo pipefail
if ! command -v cargo-zigbuild >/dev/null; then
echo "cargo-zigbuild not found. install with: cargo install cargo-zigbuild && brew install zig" >&2
exit 1
fi
BASE="bindings/dotnet/src/Dreport.Service/runtimes"
build_target() {
local TARGET=$1 RID=$2 PREFIX=$3 EXT=$4
rustup target add "$TARGET" >/dev/null
cargo zigbuild --release -p dreport-ffi --target "$TARGET"
mkdir -p "$BASE/$RID/native"
cp "target/$TARGET/release/${PREFIX}dreport_ffi.$EXT" \
"$BASE/$RID/native/${PREFIX}dreport_ffi.$EXT"
echo "$RID"
}
build_target aarch64-apple-darwin osx-arm64 lib dylib
build_target x86_64-apple-darwin osx-x64 lib dylib
build_target x86_64-unknown-linux-gnu linux-x64 lib so
build_target aarch64-unknown-linux-gnu linux-arm64 lib so
build_target x86_64-pc-windows-gnu win-x64 "" dll
# Generate a nuspec for whatever RIDs currently sit in runtimes/, then pack
# the Dreport.Service NuGet package.
nuget-pack:
#!/usr/bin/env bash
set -euo pipefail
PROJ_DIR="bindings/dotnet/src/Dreport.Service"
NUSPEC_NAME=".generated.nuspec"
NUSPEC="$PROJ_DIR/$NUSPEC_NAME"
OUT_DIR="$(pwd)/target/nuget"
mkdir -p "$OUT_DIR"
dotnet build "$PROJ_DIR/Dreport.Service.csproj" -c Release --nologo
# <files> entries for every native binary that exists on disk.
FILES=""
while IFS= read -r path; do
rel="${path#${PROJ_DIR}/}"
FILES+=" <file src=\"$rel\" target=\"$rel\" />"$'\n'
done < <(find "$PROJ_DIR/runtimes" -type f \( -name '*.dylib' -o -name '*.so' -o -name '*.dll' \) 2>/dev/null | sort)
{
echo '<?xml version="1.0" encoding="utf-8"?>'
echo '<package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd">'
echo ' <metadata>'
echo ' <id>Dreport.Service</id>'
echo " <version>{{NUGET_VERSION}}</version>"
echo ' <authors>dreport</authors>'
echo ' <description>Native layout engine + PDF renderer for dreport templates. Wraps the dreport-ffi C ABI.</description>'
echo ' <tags>pdf layout template rendering</tags>'
echo ' <dependencies><group targetFramework="net8.0" /></dependencies>'
echo ' </metadata>'
echo ' <files>'
echo ' <file src="bin/Release/net8.0/Dreport.Service.dll" target="lib/net8.0/Dreport.Service.dll" />'
printf '%s' "$FILES"
echo ' </files>'
echo '</package>'
} > "$NUSPEC"
rm -f "$OUT_DIR/Dreport.Service."*.nupkg
dotnet pack "$PROJ_DIR/Dreport.Service.csproj" \
-c Release --no-build --nologo \
-p:NuspecFile="$NUSPEC_NAME" \
-p:NuspecBasePath="." \
-p:IsPackable=true \
-o "$OUT_DIR"
echo "package -> $OUT_DIR/Dreport.Service.{{NUGET_VERSION}}.nupkg"
unzip -l "$OUT_DIR/Dreport.Service.{{NUGET_VERSION}}.nupkg"
# Push the packed nupkg to Gitea.
nuget-push:
dotnet nuget push \
"target/nuget/Dreport.Service.{{NUGET_VERSION}}.nupkg" \
--source "{{NUGET_REGISTRY_URL}}" \
--api-key "{{NUGET_TOKEN}}" \
--skip-duplicate
# Pack Dreport.AspNetCore (depends on Dreport.Service via NuGet dependency).
nuget-pack-aspnetcore:
#!/usr/bin/env bash
set -euo pipefail
PROJ_DIR="bindings/dotnet/src/Dreport.AspNetCore"
NUSPEC_NAME=".generated.nuspec"
NUSPEC="$PROJ_DIR/$NUSPEC_NAME"
OUT_DIR="$(pwd)/target/nuget"
mkdir -p "$OUT_DIR"
dotnet build "$PROJ_DIR/Dreport.AspNetCore.csproj" -c Release --nologo
{
echo '<?xml version="1.0" encoding="utf-8"?>'
echo '<package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd">'
echo ' <metadata>'
echo ' <id>Dreport.AspNetCore</id>'
echo " <version>{{NUGET_VERSION}}</version>"
echo ' <authors>dreport</authors>'
echo ' <description>ASP.NET Core integration for Dreport.Service: DI registration plus optional /api endpoint mapping.</description>'
echo ' <tags>pdf layout aspnetcore dreport</tags>'
echo ' <dependencies>'
echo ' <group targetFramework="net8.0">'
echo " <dependency id=\"Dreport.Service\" version=\"{{NUGET_VERSION}}\" />"
echo ' </group>'
echo ' </dependencies>'
echo ' <frameworkReferences>'
echo ' <group targetFramework="net8.0">'
echo ' <frameworkReference name="Microsoft.AspNetCore.App" />'
echo ' </group>'
echo ' </frameworkReferences>'
echo ' </metadata>'
echo ' <files>'
echo ' <file src="bin/Release/net8.0/Dreport.AspNetCore.dll" target="lib/net8.0/Dreport.AspNetCore.dll" />'
echo ' </files>'
echo '</package>'
} > "$NUSPEC"
rm -f "$OUT_DIR/Dreport.AspNetCore."*.nupkg
dotnet pack "$PROJ_DIR/Dreport.AspNetCore.csproj" \
-c Release --no-build --nologo \
-p:NuspecFile="$NUSPEC_NAME" \
-p:NuspecBasePath="." \
-p:IsPackable=true \
-o "$OUT_DIR"
echo "package -> $OUT_DIR/Dreport.AspNetCore.{{NUGET_VERSION}}.nupkg"
unzip -l "$OUT_DIR/Dreport.AspNetCore.{{NUGET_VERSION}}.nupkg"
# Push Dreport.AspNetCore to Gitea.
nuget-push-aspnetcore:
dotnet nuget push \
"target/nuget/Dreport.AspNetCore.{{NUGET_VERSION}}.nupkg" \
--source "{{NUGET_REGISTRY_URL}}" \
--api-key "{{NUGET_TOKEN}}" \
--skip-duplicate
# Single-host publish (host RID only — fastest, good for dev iterations).
nuget-publish: nuget-build-native-host nuget-pack nuget-push nuget-pack-aspnetcore nuget-push-aspnetcore
# Multi-RID publish (osx-arm64 + osx-x64 + linux-x64 + linux-arm64 + win-x64).
# Requires cargo-zigbuild. Single command, all platforms + AspNetCore, push to Gitea.
nuget-publish-all: nuget-build-native-all nuget-pack nuget-push nuget-pack-aspnetcore nuget-push-aspnetcore