diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 968b3e1..4ffd581 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -94,19 +94,19 @@ jobs: uses: actions/cache@v4 with: path: | - ~/.cargo/bin/cargo-tauri + ~/.cargo/bin/cargo-tauri* ~/.cargo/registry ~/.cargo/git key: ${{ runner.os }}-${{ matrix.target }}-cargo-tauri-v2-${{ hashFiles('src-tauri/Cargo.lock') }} - name: Install Tauri CLI (cargo plugin) - if: steps.cache-tauri-cli.outputs.cache-hit != 'true' - run: cargo install tauri-cli --locked + shell: bash + run: cargo tauri --version || cargo install tauri-cli --locked - name: Install dependencies (macOS) if: matrix.platform == 'macos-latest' run: | - brew install create-dmg + brew install apache-arrow create-dmg libomp - name: Install dependencies (Linux) if: matrix.platform == 'ubuntu-latest' || matrix.platform == 'ubuntu-24.04-arm' @@ -171,6 +171,15 @@ jobs: LBUG_TARGET_DIR: ${{ github.workspace }}/src-tauri/liblbug run: bash scripts/download-liblbug.sh + - name: Download icebug + env: + ICEBUG_TARGET_DIR: ${{ github.workspace }}/src-tauri/icebug + run: bash scripts/download_icebug.sh + + - name: Stage macOS dylibs + if: matrix.platform == 'macos-latest' + run: bash scripts/stage_macos_frameworks.sh + - name: Build Tauri app (Linux) if: matrix.platform == 'ubuntu-latest' || matrix.platform == 'ubuntu-24.04-arm' run: pnpm tauri build --target ${{ matrix.target }} --bundles ${{ matrix.bundles }} --verbose @@ -181,8 +190,14 @@ jobs: LD_LIBRARY_PATH: ${{ github.workspace }}/src-tauri/liblbug NO_STRIP: "true" # https://github.com/tauri-apps/tauri/issues/14796 - - name: Build Tauri app (macOS/Windows) - if: matrix.platform == 'macos-latest' || matrix.platform == 'windows-latest' + - name: Build Tauri app (macOS) + if: matrix.platform == 'macos-latest' + run: pnpm tauri build --target ${{ matrix.target }} --bundles ${{ matrix.bundles }} --features icebug-analytics --verbose + env: + CXXFLAGS: -I/opt/homebrew/opt/libomp/include + + - name: Build Tauri app (Windows) + if: matrix.platform == 'windows-latest' run: pnpm tauri build --target ${{ matrix.target }} --bundles ${{ matrix.bundles }} --verbose - name: Upload artifacts diff --git a/.gitignore b/.gitignore index 3100e22..23af211 100644 --- a/.gitignore +++ b/.gitignore @@ -32,4 +32,6 @@ pnpm-lock.yaml # Tauri src-tauri/target/ src-tauri/liblbug/ +src-tauri/icebug/ +src-tauri/macos-frameworks/ src-tauri/gen/ diff --git a/package.json b/package.json index a779a6c..e9662c1 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "version": "0.15.1", "type": "module", "scripts": { - "setup": "bash scripts/download-liblbug.sh", + "setup": "bash scripts/download-liblbug.sh && bash scripts/download_icebug.sh && bash scripts/stage_macos_frameworks.sh", "dev:frontend": "vite", "build:frontend": "tsc -b && vite build", "tauri": "cargo tauri", diff --git a/scripts/download_icebug.sh b/scripts/download_icebug.sh new file mode 100755 index 0000000..c785d84 --- /dev/null +++ b/scripts/download_icebug.sh @@ -0,0 +1,89 @@ +#!/usr/bin/env bash +# Download the platform-specific Icebug prebuilt into src-tauri/icebug. +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" + +REPOSITORY="${ICEBUG_GITHUB_REPOSITORY:-Ladybug-Memory/icebug}" +TARGET_DIR="${ICEBUG_TARGET_DIR:-$PROJECT_DIR/src-tauri/icebug}" +VERSION="${ICEBUG_VERSION:-12.8}" + +OS="$(uname -s)" +ARCH="$(uname -m)" + +case "$OS" in + Darwin) + ASSET_OS="macos" + ;; + Linux) + ASSET_OS="linux" + ;; + MINGW*|MSYS*|CYGWIN*) + ASSET_OS="win" + ;; + *) + echo "Unsupported OS: $OS" >&2 + exit 1 + ;; +esac + +case "$ARCH" in + arm64|aarch64) + ASSET_ARCH="arm64" + ;; + x86_64) + if [ "$ASSET_OS" = "win" ]; then + ASSET_ARCH="amd64" + else + ASSET_ARCH="x86_64" + fi + ;; + *) + echo "Unsupported architecture: $ARCH" >&2 + exit 1 + ;; +esac + +if [ "$ASSET_OS" = "win" ]; then + ARCHIVE="icebug-${ASSET_OS}-${ASSET_ARCH}.zip" +else + ARCHIVE="icebug-${ASSET_OS}-${ASSET_ARCH}.tar.gz" +fi + +LIB_NAME="libnetworkit.so" +case "$ASSET_OS" in + macos) + LIB_NAME="libnetworkit.dylib" + ;; + win) + LIB_NAME="networkit.lib" + ;; +esac + +if [ -f "$TARGET_DIR/lib/$LIB_NAME" ] && [ -d "$TARGET_DIR/include/networkit" ]; then + echo "icebug already exists in $TARGET_DIR" + exit 0 +fi + +mkdir -p "$TARGET_DIR" +TMPDIR="$(mktemp -d)" +trap 'rm -rf "$TMPDIR"' EXIT + +DOWNLOAD_URL="https://github.com/${REPOSITORY}/releases/download/${VERSION}/${ARCHIVE}" +echo "Downloading $DOWNLOAD_URL ..." +curl -fSL "$DOWNLOAD_URL" -o "$TMPDIR/$ARCHIVE" + +rm -rf "$TARGET_DIR/include" "$TARGET_DIR/lib" "$TARGET_DIR/extlibs" +if [[ "$ARCHIVE" == *.zip ]]; then + unzip -q "$TMPDIR/$ARCHIVE" -d "$TARGET_DIR" +else + tar xzf "$TMPDIR/$ARCHIVE" -C "$TARGET_DIR" +fi + +if [ ! -f "$TARGET_DIR/lib/$LIB_NAME" ]; then + echo "Expected Icebug library not found at $TARGET_DIR/lib/$LIB_NAME" >&2 + exit 1 +fi + +echo "Installed $ARCHIVE to $TARGET_DIR" diff --git a/scripts/patch_macos_bundle_dylibs.sh b/scripts/patch_macos_bundle_dylibs.sh new file mode 100755 index 0000000..2e4f222 --- /dev/null +++ b/scripts/patch_macos_bundle_dylibs.sh @@ -0,0 +1,96 @@ +#!/usr/bin/env bash +# Rewrite macOS release binaries to load bundled dylibs from Contents/Frameworks. +set -euo pipefail + +if [ "$(uname -s)" != "Darwin" ]; then + echo "Skipping macOS dylib patching on $(uname -s)" + exit 0 +fi + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" +TAURI_DIR="$PROJECT_DIR/src-tauri" + +find_frameworks_dirs() { + find "$TAURI_DIR/target" -type d -name Frameworks -print 2>/dev/null +} + +change_if_present() { + local binary="$1" + local old="$2" + local new="$3" + + if otool -L "$binary" | awk '{ print $1 }' | grep -Fxq "$old"; then + install_name_tool -change "$old" "$new" "$binary" + fi +} + +patch_binary() { + local binary="$1" + + while IFS= read -r dep; do + case "$dep" in + *libarrow*.dylib) + change_if_present "$binary" "$dep" "@rpath/libarrow.icebug.dylib" + ;; + *libomp.dylib) + change_if_present "$binary" "$dep" "@rpath/libomp.dylib" + ;; + *libnetworkit.dylib) + change_if_present "$binary" "$dep" "@rpath/libnetworkit.dylib" + ;; + *liblbug*.dylib) + change_if_present "$binary" "$dep" "@rpath/liblbug.0.dylib" + ;; + esac + done < <(otool -L "$binary" | awk 'NR > 1 { print $1 }') +} + +patched_any=0 +while IFS= read -r binary; do + chmod u+w "$binary" + patch_binary "$binary" + patched_any=1 + echo "Patched load commands in $binary" +done < <(find "$TAURI_DIR/target" -path '*/release/bugscope' -type f -perm -111 -print 2>/dev/null) + +if [ "$patched_any" -eq 0 ]; then + echo "Could not find a release bugscope binary under $TAURI_DIR/target" >&2 + exit 1 +fi + +while IFS= read -r frameworks_dir; do + [ -d "$frameworks_dir" ] || continue + + for dylib in "$frameworks_dir"/*.dylib; do + [ -f "$dylib" ] || continue + chmod u+w "$dylib" + done + + if [ -f "$frameworks_dir/libarrow.icebug.dylib" ]; then + install_name_tool -id "@rpath/libarrow.icebug.dylib" "$frameworks_dir/libarrow.icebug.dylib" + patch_binary "$frameworks_dir/libarrow.icebug.dylib" + fi + + if [ -f "$frameworks_dir/libnetworkit.dylib" ]; then + install_name_tool -id "@rpath/libnetworkit.dylib" "$frameworks_dir/libnetworkit.dylib" + patch_binary "$frameworks_dir/libnetworkit.dylib" + fi + + if [ -f "$frameworks_dir/liblbug.0.dylib" ]; then + install_name_tool -id "@rpath/liblbug.0.dylib" "$frameworks_dir/liblbug.0.dylib" + patch_binary "$frameworks_dir/liblbug.0.dylib" + fi + + if [ -f "$frameworks_dir/liblbug.dylib" ]; then + install_name_tool -id "@rpath/liblbug.dylib" "$frameworks_dir/liblbug.dylib" + patch_binary "$frameworks_dir/liblbug.dylib" + fi + + if [ -f "$frameworks_dir/libomp.dylib" ]; then + install_name_tool -id "@rpath/libomp.dylib" "$frameworks_dir/libomp.dylib" + patch_binary "$frameworks_dir/libomp.dylib" + fi + + echo "Patched bundled dylibs in $frameworks_dir" +done < <(find_frameworks_dirs) diff --git a/scripts/stage_macos_frameworks.sh b/scripts/stage_macos_frameworks.sh new file mode 100755 index 0000000..53eda14 --- /dev/null +++ b/scripts/stage_macos_frameworks.sh @@ -0,0 +1,110 @@ +#!/usr/bin/env bash +# Stage dylibs that Tauri should copy into Contents/Frameworks on macOS. +set -euo pipefail + +if [ "$(uname -s)" != "Darwin" ]; then + echo "Skipping macOS framework staging on $(uname -s)" + exit 0 +fi + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" +TAURI_DIR="$PROJECT_DIR/src-tauri" +ICEBUG_DIR="${ICEBUG_DIR:-$TAURI_DIR/icebug}" +FRAMEWORKS_DIR="$TAURI_DIR/macos-frameworks" + +NETWORKIT_SRC="$ICEBUG_DIR/lib/libnetworkit.dylib" +LBUG_SRC="$TAURI_DIR/liblbug/liblbug.0.dylib" + +if [ ! -f "$NETWORKIT_SRC" ]; then + echo "Missing $NETWORKIT_SRC. Run scripts/download_icebug.sh first." >&2 + exit 1 +fi + +if [ ! -f "$LBUG_SRC" ]; then + echo "Missing $LBUG_SRC. Run scripts/download-liblbug.sh first." >&2 + exit 1 +fi + +dep_path() { + local lib="$1" + local pattern="$2" + otool -L "$lib" | awk -v pattern="$pattern" '$1 ~ pattern { print $1; exit }' +} + +resolve_dep() { + local dep="$1" + local loader_dir="$2" + + case "$dep" in + @loader_path/*) + echo "$loader_dir/${dep#@loader_path/}" + ;; + @rpath/*) + local name="${dep#@rpath/}" + if [ -f "$ICEBUG_DIR/lib/$name" ]; then + echo "$ICEBUG_DIR/lib/$name" + elif [ -f "/opt/homebrew/opt/apache-arrow/lib/$name" ]; then + echo "/opt/homebrew/opt/apache-arrow/lib/$name" + elif [ -f "/usr/local/opt/apache-arrow/lib/$name" ]; then + echo "/usr/local/opt/apache-arrow/lib/$name" + elif [ -f "/opt/homebrew/opt/libomp/lib/$name" ]; then + echo "/opt/homebrew/opt/libomp/lib/$name" + elif [ -f "/usr/local/opt/libomp/lib/$name" ]; then + echo "/usr/local/opt/libomp/lib/$name" + else + echo "$dep" + fi + ;; + *) + echo "$dep" + ;; + esac +} + +NETWORKIT_DIR="$(cd "$(dirname "$NETWORKIT_SRC")" && pwd)" +ARROW_DEP="$(dep_path "$NETWORKIT_SRC" 'libarrow[.].*[.]dylib')" +OMP_DEP="$(dep_path "$NETWORKIT_SRC" 'libomp[.]dylib')" + +if [ -z "$ARROW_DEP" ]; then + echo "Could not find libarrow dependency in $NETWORKIT_SRC" >&2 + exit 1 +fi + +ARROW_SRC="$(resolve_dep "$ARROW_DEP" "$NETWORKIT_DIR")" +if [ ! -f "$ARROW_SRC" ]; then + echo "Icebug requires $ARROW_DEP, but it was not found at $ARROW_SRC" >&2 + echo "Install the matching Apache Arrow C++ package before building." >&2 + exit 1 +fi + +if [ -n "$OMP_DEP" ]; then + OMP_SRC="$(resolve_dep "$OMP_DEP" "$NETWORKIT_DIR")" + if [ ! -f "$OMP_SRC" ]; then + echo "Icebug requires $OMP_DEP, but it was not found at $OMP_SRC" >&2 + echo "Install libomp before building." >&2 + exit 1 + fi +else + OMP_SRC="" +fi + +mkdir -p "$FRAMEWORKS_DIR" +cp -f "$NETWORKIT_SRC" "$FRAMEWORKS_DIR/libnetworkit.dylib" +cp -f "$ARROW_SRC" "$FRAMEWORKS_DIR/libarrow.icebug.dylib" +cp -f "$LBUG_SRC" "$FRAMEWORKS_DIR/liblbug.0.dylib" +cp -f "$LBUG_SRC" "$FRAMEWORKS_DIR/liblbug.dylib" + +if [ -n "$OMP_SRC" ]; then + cp -f "$OMP_SRC" "$FRAMEWORKS_DIR/libomp.dylib" +fi + +chmod u+w "$FRAMEWORKS_DIR"/*.dylib + +echo "Staged macOS frameworks in $FRAMEWORKS_DIR" +echo " libnetworkit.dylib <= $NETWORKIT_SRC" +echo " libarrow.icebug.dylib <= $ARROW_SRC (from $ARROW_DEP)" +echo " liblbug.0.dylib <= $LBUG_SRC" +if [ -n "$OMP_SRC" ]; then + echo " libomp.dylib <= $OMP_SRC" +fi diff --git a/src-tauri/.cargo/config.toml b/src-tauri/.cargo/config.toml index 40558af..de46d08 100644 --- a/src-tauri/.cargo/config.toml +++ b/src-tauri/.cargo/config.toml @@ -5,6 +5,7 @@ LBUG_LIBRARY_DIR = { value = "liblbug", relative = true } LBUG_INCLUDE_DIR = { value = "liblbug", relative = true } LBUG_SHARED = "1" +ICEBUG_DIR = { value = "icebug", relative = true } # Embed rpath so the binary can find liblbug.dylib at runtime. # First rpath: for production .app bundles (Contents/Frameworks/) diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index e04ce4a..d3d976c 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -7,7 +7,8 @@ "frontendDist": "../dist", "devUrl": "http://localhost:5173", "beforeDevCommand": "npm run dev:frontend", - "beforeBuildCommand": "npm run build:frontend" + "beforeBuildCommand": "npm run build:frontend && bash scripts/stage_macos_frameworks.sh", + "beforeBundleCommand": "bash scripts/patch_macos_bundle_dylibs.sh" }, "app": { "windows": [ @@ -32,7 +33,13 @@ "icons/icon.png" ], "macOS": { - "frameworks": ["liblbug/liblbug.dylib"], + "frameworks": [ + "macos-frameworks/liblbug.0.dylib", + "macos-frameworks/liblbug.dylib", + "macos-frameworks/libnetworkit.dylib", + "macos-frameworks/libarrow.icebug.dylib", + "macos-frameworks/libomp.dylib" + ], "minimumSystemVersion": "13.3" } }