diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index fb82c08..44cbc18 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -9,8 +9,9 @@ on: workflow_dispatch: permissions: - actions: read + actions: write contents: read + packages: write concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -56,7 +57,7 @@ jobs: - platform: ubuntu-24.04-arm target: aarch64-unknown-linux-gnu bundles: "appimage deb" - - platform: windows-latest + - platform: windows-2022 target: x86_64-pc-windows-msvc bundles: "msi nsis" @@ -106,8 +107,14 @@ jobs: - name: Install dependencies (Linux) if: matrix.platform == 'ubuntu-latest' || matrix.platform == 'ubuntu-24.04-arm' run: | + # LLVM repo for libomp matching icebug prebuilt binaries (compiled with clang-20) + wget -qO- https://apt.llvm.org/llvm-snapshot.gpg.key | sudo tee /etc/apt/trusted.gpg.d/llvm-snapshot.asc + echo "deb http://apt.llvm.org/noble llvm-toolchain-noble-20 main" | sudo tee /etc/apt/sources.list.d/llvm20.list + # Apache Arrow repo + wget https://apache.jfrog.io/artifactory/arrow/ubuntu/apache-arrow-apt-source-latest-noble.deb + sudo apt-get install -y ./apache-arrow-apt-source-latest-noble.deb sudo apt-get update - sudo apt-get install -y libgtk-3-dev libgirepository1.0-dev libwebkit2gtk-4.1-dev libappindicator3-dev patchelf librsvg2-dev libfuse2 + sudo apt-get install -y libgtk-3-dev libgirepository1.0-dev libwebkit2gtk-4.1-dev libappindicator3-dev patchelf librsvg2-dev libfuse2 libarrow-dev libomp-20-dev - name: Install frontend dependencies run: npm ci @@ -181,8 +188,8 @@ jobs: env: # Needed for linuxdeploy AppImage bundling on CI runners APPIMAGE_EXTRACT_AND_RUN: "1" - # Ensure linuxdeploy can resolve and bundle liblbug.so - LD_LIBRARY_PATH: ${{ github.workspace }}/src-tauri/liblbug + # Ensure linuxdeploy can resolve and bundle liblbug.so and libnetworkit.so + LD_LIBRARY_PATH: ${{ github.workspace }}/src-tauri/liblbug:${{ github.workspace }}/src-tauri/icebug/lib NO_STRIP: "true" # https://github.com/tauri-apps/tauri/issues/14796 - name: Build Tauri app (macOS) @@ -191,9 +198,52 @@ jobs: env: CXXFLAGS: -I/opt/homebrew/opt/libomp/include + - name: Create vcpkg manifest (Windows) + if: matrix.platform == 'windows-2022' + shell: bash + run: | + cat > vcpkg.json << 'EOF' + { + "name": "bugscope-deps", + "version": "1.0.0", + "dependencies": [ + "arrow", + "pkgconf" + ] + } + EOF + + - name: Setup vcpkg (Windows) + if: matrix.platform == 'windows-2022' + uses: lukka/run-vcpkg@v11 + with: + vcpkgGitCommitId: 'b322364f06308bdd24823f9d8f03fe0cc86fd46f' + + - name: Authenticate vcpkg NuGet (Windows) + if: matrix.platform == 'windows-2022' + shell: pwsh + run: | + $nuget = & $env:VCPKG_ROOT/vcpkg fetch nuget | Out-String + $nuget = $nuget.Trim() + & $nuget sources add -Source "https://nuget.pkg.github.com/${{ github.repository_owner }}/index.json" -Name "GitHubPackages" -Username "${{ github.repository_owner }}" -Password "${{ secrets.GITHUB_TOKEN }}" -StorePasswordInClearText -Verbosity detailed + & $nuget setapikey "${{ secrets.GITHUB_TOKEN }}" -Source "https://nuget.pkg.github.com/${{ github.repository_owner }}/index.json" + + - name: Install vcpkg packages (Windows) + if: matrix.platform == 'windows-2022' + shell: pwsh + run: | + & $env:VCPKG_ROOT/vcpkg install --recurse --clean-after-build --triplet x64-windows + env: + VCPKG_BINARY_SOURCES: 'clear;nuget,https://nuget.pkg.github.com/${{ github.repository_owner }}/index.json,readwrite' + - name: Build Tauri app (Windows) - if: matrix.platform == 'windows-latest' - run: pnpm tauri build --target ${{ matrix.target }} --bundles ${{ matrix.bundles }} --verbose + if: matrix.platform == 'windows-2022' + shell: pwsh + run: | + $env:PKG_CONFIG_PATH = "$env:GITHUB_WORKSPACE\vcpkg_installed\x64-windows\lib\pkgconfig" + $env:PATH = "$env:GITHUB_WORKSPACE\vcpkg_installed\x64-windows\tools\pkgconf;$env:GITHUB_WORKSPACE\src-tauri\icebug\lib;$env:PATH" + $env:CXXFLAGS = "/std:c++20" + pnpm tauri build --target ${{ matrix.target }} --bundles ${{ matrix.bundles }} --verbose - name: Upload artifacts uses: actions/upload-artifact@v4 diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index f051566..337aeaa 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -1896,9 +1896,9 @@ dependencies = [ [[package]] name = "icebug" -version = "12.9.0" +version = "13.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "faefb8196c2e3a749df1035bf9f767b9ef2fa93e1b24734adb07ed5e75217c64" +checksum = "7cfcf9aca78620ee7174f4a9c5afe64f7f12d85b73f5819c24213d426cdf252e" dependencies = [ "arrow-array 56.2.1", "arrow-buffer 56.2.1", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index c98c608..5aa7d86 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -11,7 +11,7 @@ crate-type = ["staticlib", "cdylib", "rlib"] tauri-build = { version = "2", features = [] } [features] -default = [] +default = ["icebug-analytics"] icebug-analytics = ["dep:icebug"] [dependencies] @@ -25,5 +25,5 @@ walkdir = "2" dirs = "5" fastrand = "2" reqwest = { version = "0.13.3", features = ["blocking", "json"] } -icebug = { version = "12.9.0", optional = true } +icebug = { version = "13.0.0", optional = true } lbug = { version = "0.18.1", features = ["arrow"] } diff --git a/src-tauri/build.rs b/src-tauri/build.rs index f1d433e..657530b 100644 --- a/src-tauri/build.rs +++ b/src-tauri/build.rs @@ -1,11 +1,14 @@ +use std::path::PathBuf; + fn main() { // LBUG_LIBRARY_DIR, LBUG_INCLUDE_DIR, and LBUG_SHARED are set in .cargo/config.toml // to point at the prebuilt liblbug in src-tauri/liblbug/. This tells the lbug crate's // build script to skip CMake compilation and link against the prebuilt shared library. - // Check that the prebuilt library exists before building. let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); - let liblbug_dir = std::path::Path::new(&manifest_dir).join("liblbug"); + + // Check that the prebuilt liblbug exists before building. + let liblbug_dir = PathBuf::from(&manifest_dir).join("liblbug"); if !liblbug_dir.exists() { panic!( "\n\n\ @@ -18,5 +21,25 @@ fn main() { ); } + // Windows-specific: The icebug v13 Windows release splits Networkit into + // lib/networkit.lib (static, with dllimport for GlobalState) and + // lib/networkit/networkit_state.lib (DLL import lib exporting GlobalState). + // The icebug crate's build.rs only links networkit.lib, missing GlobalState. + // We add the missing import library here. + // + // ICEBUG_DIR is set in .cargo/config.toml (e.g. "icebug" relative to the config dir, + // resolved to an absolute path by cargo). + let target = std::env::var("TARGET").unwrap_or_default(); + if target.contains("windows") { + if let Some(icebug_dir) = std::env::var_os("ICEBUG_DIR") { + let search = PathBuf::from(&icebug_dir).join("lib").join("networkit"); + if search.join("networkit_state.lib").exists() { + println!("cargo:rustc-link-search=native={}", search.display()); + println!("cargo:rustc-link-lib=dylib=networkit_state"); + println!("cargo:warning=Linked networkit_state.lib for GlobalState symbols"); + } + } + } + tauri_build::build() } diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 2663eab..274ded4 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -214,31 +214,48 @@ fn scan_for_databases(dir: &Path, base_dir: &Path) -> Vec { for entry in WalkDir::new(dir).into_iter().filter_map(|e| e.ok()) { let path = entry.path(); if path.is_file() { - if let Some(ext) = path.extension() { - if ext == "lbdb" { - let name = path - .file_stem() - .unwrap_or_default() - .to_string_lossy() - .to_string(); - let relative_path = path - .strip_prefix(base_dir) - .unwrap_or(path) - .to_string_lossy() - .to_string(); - databases.push(DatabaseInfo { - id: 0, // assigned later - name, - path: path.to_string_lossy().to_string(), - relative_path, - }); - } + let abs_path = path.to_string_lossy().to_string(); + // Let Database::new() validate by magic bytes — no extension whitelist. + // A quick probe to skip obviously non-DB files without a full open. + if !is_lbug_file_fast(&abs_path) { + continue; } + let name = path + .file_stem() + .unwrap_or_default() + .to_string_lossy() + .to_string(); + let relative_path = path + .strip_prefix(base_dir) + .unwrap_or(path) + .to_string_lossy() + .to_string(); + databases.push(DatabaseInfo { + id: 0, // assigned later + name, + path: abs_path, + relative_path, + }); } } databases } +/// Quick check that a file starts with Lbug's magic bytes, without a full database open. +/// The Lbug magic is the 4-byte sequence `b"LBUG"` at offset 0. +fn is_lbug_file_fast(path: &str) -> bool { + use std::io::Read; + let mut file = match std::fs::File::open(path) { + Ok(f) => f, + Err(_) => return false, + }; + let mut magic = [0u8; 4]; + if file.read_exact(&mut magic).is_err() { + return false; + } + &magic == b"LBUG" +} + fn get_all_databases(state: &AppState) -> Vec { let scanned = scan_for_databases(&state.data_dir, &state.data_dir); let custom = state.custom_databases.lock().unwrap(); @@ -264,9 +281,7 @@ fn database_info_from_path(file_path: &str) -> Result { return Err("File not found".to_string()); } - if abs_path.extension().and_then(|e| e.to_str()) != Some("lbdb") { - return Err("Only .lbdb files are supported".to_string()); - } + // Extension check removed: Database::new() validates by magic bytes and returns a clear error let abs_path_str = abs_path.to_string_lossy().to_string(); let name = abs_path @@ -1318,6 +1333,20 @@ fn collect_edge_graph(conn: &Connection, limit: usize) -> Result stem.to_string_lossy().to_string(), + None => name.clone(), + }; files.push(DirEntry { - name: name.trim_end_matches(".lbdb").to_string(), + name: trimmed, path: entry_path.to_string_lossy().to_string(), entry_type: "file".to_string(), });