diff --git a/.gitignore b/.gitignore index cb8e595..04e8e05 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ /.vscode/ /build/ +/build_release/ +/dist/ /cmake-build-debug/ diff --git a/CMakeLists.txt b/CMakeLists.txt index 014bd95..ec14971 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,9 +1,15 @@ cmake_minimum_required(VERSION 3.30) -project(tundradb) +project(tundradb VERSION 1.0.0 LANGUAGES CXX C) set(CMAKE_CXX_STANDARD 23) set(CMAKE_CXX_STANDARD_REQUIRED ON) +# Option: build shared or static library (default: static) +option(BUILD_SHARED_LIBS "Build TundraDB as a shared library" OFF) +option(TUNDRADB_BUILD_SHELL "Build the tundra_shell interactive CLI" ON) +option(TUNDRADB_BUILD_TESTS "Build tests" ON) +option(TUNDRADB_BUILD_BENCHMARKS "Build benchmarks" ON) + # Configure compile-time logging levels if(CMAKE_BUILD_TYPE STREQUAL "Debug") add_compile_definitions(TUNDRA_LOG_LEVEL_DEBUG) @@ -37,19 +43,21 @@ find_package(Parquet REQUIRED) find_package(ArrowCompute QUIET) find_package(ArrowAcero QUIET) -add_executable(tundra_bench_runner bench/tundra_runner.cpp) -target_link_libraries(tundra_bench_runner - PRIVATE - core - Arrow::arrow_shared - ${ARROW_DATASET_LIB} - Parquet::parquet_shared - ${UUID_LIBRARY} - ${ANTLR4_RUNTIME} - LLVMSupport - LLVMCore - TBB::tbb -) +if(TUNDRADB_BUILD_BENCHMARKS) + add_executable(tundra_bench_runner bench/tundra_runner.cpp) + target_link_libraries(tundra_bench_runner + PRIVATE + core + Arrow::arrow_shared + ${ARROW_DATASET_LIB} + Parquet::parquet_shared + ${UUID_LIBRARY} + ${ANTLR4_RUNTIME} + LLVMSupport + LLVMCore + TBB::tbb + ) +endif() # Find CDS library @@ -230,7 +238,9 @@ add_library(core target_include_directories(core PUBLIC - ${CMAKE_CURRENT_SOURCE_DIR}/include + $ + $ + $ PRIVATE ${ARROW_INCLUDE_DIR} /usr/local/include @@ -263,151 +273,240 @@ endif() # uuid # Directly use the library name #) -# Main executable -add_executable(tundradb src/main.cpp) -target_link_libraries(tundradb - PRIVATE - core - Arrow::arrow_shared - ${ARROW_DATASET_LIB} - Parquet::parquet_shared - ${UUID_LIBRARY} -) +# --------------------------------------------------------------------------- +# ANTLR Integration (needed by shell) +# --------------------------------------------------------------------------- +if(TUNDRADB_BUILD_SHELL) + # Find Java for running ANTLR generator + find_package(Java REQUIRED) + + # Define ANTLR jar location + set(ANTLR_JAR_LOCATION "${CMAKE_CURRENT_SOURCE_DIR}/antlr/lib/antlr-4.13.2-complete.jar") + + # Set up the output directory + set(ANTLR_OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/antlr_generated") + file(MAKE_DIRECTORY ${ANTLR_OUTPUT_DIR}) + + # Define generated files + set(TUNDRAQL_GENERATED_SRC + ${ANTLR_OUTPUT_DIR}/TundraQLLexer.cpp + ${ANTLR_OUTPUT_DIR}/TundraQLParser.cpp + ${ANTLR_OUTPUT_DIR}/TundraQLBaseListener.cpp + ${ANTLR_OUTPUT_DIR}/TundraQLListener.cpp + ${ANTLR_OUTPUT_DIR}/TundraQLBaseVisitor.cpp + ${ANTLR_OUTPUT_DIR}/TundraQLVisitor.cpp + ) -# Interactive shell executable -add_executable(tundra_shell src/tundra_shell.cpp libs/linenoise/linenoise.c) + # Mark files as generated + foreach(src_file ${TUNDRAQL_GENERATED_SRC}) + set_source_files_properties( + ${src_file} + PROPERTIES + GENERATED TRUE + ) + endforeach() + + # Custom target for ANTLR parser generation + add_custom_target(GenerateTundraQLParser DEPENDS ${TUNDRAQL_GENERATED_SRC}) + + # Custom command to generate ANTLR files + add_custom_command( + OUTPUT ${TUNDRAQL_GENERATED_SRC} + COMMAND + ${Java_JAVA_EXECUTABLE} -jar ${ANTLR_JAR_LOCATION} -Werror -Dlanguage=Cpp -listener -visitor -o ${ANTLR_OUTPUT_DIR} -package tundraql ${CMAKE_CURRENT_SOURCE_DIR}/antlr/TundraQL.g4 + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/antlr/TundraQL.g4 + WORKING_DIRECTORY "${CMAKE_BINARY_DIR}" + COMMENT "Generating ANTLR4 parser" + ) -# Add ANTLR generated include directory to tundra_shell -target_include_directories(tundra_shell - PRIVATE - ${ANTLR_OUTPUT_DIR} -) + # Add ANTLR runtime library + if(APPLE) + find_library(ANTLR4_RUNTIME antlr4-runtime PATHS /opt/homebrew/lib /usr/local/lib) + include_directories(/opt/homebrew/include/antlr4-runtime /usr/local/include/antlr4-runtime) + else() + find_library(ANTLR4_RUNTIME antlr4-runtime PATHS /usr/local/lib /usr/lib /usr/lib/x86_64-linux-gnu) + find_path(ANTLR4_INCLUDE_DIR antlr4-runtime.h PATHS + /usr/local/include/antlr4-runtime + /usr/include/antlr4-runtime + /usr/local/include + /usr/include + ) + if(ANTLR4_INCLUDE_DIR) + include_directories(${ANTLR4_INCLUDE_DIR}) + message(STATUS "Found ANTLR4 headers at: ${ANTLR4_INCLUDE_DIR}") + else() + include_directories(/usr/local/include) + message(WARNING "ANTLR4 headers not found in expected paths, using /usr/local/include as fallback") + endif() + endif() -# Make sure ANTLR files are generated before building tundra_shell -add_dependencies(tundra_shell GenerateTundraQLParser) + if(NOT ANTLR4_RUNTIME) + message(WARNING "ANTLR4 runtime library not found. Please install it via Homebrew or from source.") + else() + message(STATUS "Found ANTLR4 runtime library: ${ANTLR4_RUNTIME}") + endif() -target_link_libraries(tundra_shell - PRIVATE - core - tundraql_parser - Arrow::arrow_shared - ${ARROW_DATASET_LIB} - Parquet::parquet_shared - ${UUID_LIBRARY} - ${ANTLR4_RUNTIME} - LLVMSupport - LLVMCore -) + # Create a library for the ANTLR parser + add_library(tundraql_parser ${TUNDRAQL_GENERATED_SRC}) + add_dependencies(tundraql_parser GenerateTundraQLParser) -# ANTLR Integration -# Find Java for running ANTLR generator -find_package(Java REQUIRED) - -# Define ANTLR jar location -set(ANTLR_JAR_LOCATION "${CMAKE_CURRENT_SOURCE_DIR}/antlr/lib/antlr-4.13.2-complete.jar") - -# Set up the output directory -set(ANTLR_OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/antlr_generated") -file(MAKE_DIRECTORY ${ANTLR_OUTPUT_DIR}) - -# Define generated files -set(TUNDRAQL_GENERATED_SRC - ${ANTLR_OUTPUT_DIR}/TundraQLLexer.cpp - ${ANTLR_OUTPUT_DIR}/TundraQLParser.cpp - ${ANTLR_OUTPUT_DIR}/TundraQLBaseListener.cpp - ${ANTLR_OUTPUT_DIR}/TundraQLListener.cpp - ${ANTLR_OUTPUT_DIR}/TundraQLBaseVisitor.cpp - ${ANTLR_OUTPUT_DIR}/TundraQLVisitor.cpp -) + target_include_directories(tundraql_parser + PUBLIC + ${ANTLR_OUTPUT_DIR} + PRIVATE + /usr/local/include + /usr/local/include/antlr4-runtime + /usr/include + /usr/include/antlr4-runtime + ) -# Mark files as generated -foreach(src_file ${TUNDRAQL_GENERATED_SRC}) - set_source_files_properties( - ${src_file} - PROPERTIES - GENERATED TRUE + if(ANTLR4_RUNTIME) + target_link_libraries(tundraql_parser + PRIVATE + ${ANTLR4_RUNTIME} + ) + endif() + + # Interactive shell executable + add_executable(tundra_shell src/tundra_shell.cpp libs/linenoise/linenoise.c) + + target_include_directories(tundra_shell + PRIVATE + ${ANTLR_OUTPUT_DIR} ) -endforeach() - -# Custom target for ANTLR parser generation -add_custom_target(GenerateTundraQLParser DEPENDS ${TUNDRAQL_GENERATED_SRC}) - -# Custom command to generate ANTLR files -add_custom_command( - OUTPUT ${TUNDRAQL_GENERATED_SRC} - COMMAND - ${Java_JAVA_EXECUTABLE} -jar ${ANTLR_JAR_LOCATION} -Werror -Dlanguage=Cpp -listener -visitor -o ${ANTLR_OUTPUT_DIR} -package tundraql ${CMAKE_CURRENT_SOURCE_DIR}/antlr/TundraQL.g4 - DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/antlr/TundraQL.g4 - WORKING_DIRECTORY "${CMAKE_BINARY_DIR}" - COMMENT "Generating ANTLR4 parser" -) -# Add ANTLR runtime library -# On macOS with Homebrew, you may need to adjust these paths -if(APPLE) - find_library(ANTLR4_RUNTIME antlr4-runtime PATHS /opt/homebrew/lib /usr/local/lib) - include_directories(/opt/homebrew/include/antlr4-runtime /usr/local/include/antlr4-runtime) -else() - # Linux - look in standard locations and also check common install paths - find_library(ANTLR4_RUNTIME antlr4-runtime PATHS /usr/local/lib /usr/lib /usr/lib/x86_64-linux-gnu) - find_path(ANTLR4_INCLUDE_DIR antlr4-runtime.h PATHS - /usr/local/include/antlr4-runtime - /usr/include/antlr4-runtime - /usr/local/include - /usr/include + add_dependencies(tundra_shell GenerateTundraQLParser) + + target_link_libraries(tundra_shell + PRIVATE + core + tundraql_parser + Arrow::arrow_shared + ${ARROW_DATASET_LIB} + Parquet::parquet_shared + ${UUID_LIBRARY} + ${ANTLR4_RUNTIME} + LLVMSupport + LLVMCore ) - if(ANTLR4_INCLUDE_DIR) - include_directories(${ANTLR4_INCLUDE_DIR}) - message(STATUS "Found ANTLR4 headers at: ${ANTLR4_INCLUDE_DIR}") - else() - # Fallback: try to use the installed headers directly - include_directories(/usr/local/include) - message(WARNING "ANTLR4 headers not found in expected paths, using /usr/local/include as fallback") - endif() endif() -if(NOT ANTLR4_RUNTIME) - message(WARNING "ANTLR4 runtime library not found. Please install it via Homebrew or from source.") -else() - message(STATUS "Found ANTLR4 runtime library: ${ANTLR4_RUNTIME}") +# --------------------------------------------------------------------------- +# Tests +# --------------------------------------------------------------------------- +if(TUNDRADB_BUILD_TESTS) + enable_testing() + add_subdirectory(tests) endif() -# Create a library for the ANTLR parser -add_library(tundraql_parser - ${TUNDRAQL_GENERATED_SRC} +# --------------------------------------------------------------------------- +# Install rules – library + headers + shell + CMake package config +# --------------------------------------------------------------------------- +include(GNUInstallDirs) +include(CMakePackageConfigHelpers) + +# Install the core library +install(TARGETS core + EXPORT TundraDBTargets + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/tundradb ) -# Make sure the parser is generated first -add_dependencies(tundraql_parser GenerateTundraQLParser) +# Install public headers +install(DIRECTORY include/ + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/tundradb + FILES_MATCHING PATTERN "*.hpp" +) -# Include the generated header files and ANTLR runtime headers -target_include_directories(tundraql_parser - PUBLIC - ${ANTLR_OUTPUT_DIR} - PRIVATE - /usr/local/include - /usr/local/include/antlr4-runtime - /usr/include - /usr/include/antlr4-runtime +# Install the bundled nlohmann json header +install(FILES libs/json/json.hpp + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/tundradb ) -# Link ANTLR runtime to the parser library -if(ANTLR4_RUNTIME) - target_link_libraries(tundraql_parser - PRIVATE - ${ANTLR4_RUNTIME} +# Install the shell executable +if(TUNDRADB_BUILD_SHELL) + install(TARGETS tundra_shell + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} ) endif() -# Link the parser library to the main executable -target_link_libraries(tundradb - PRIVATE - tundraql_parser +# Generate the version file +write_basic_package_version_file( + "${CMAKE_CURRENT_BINARY_DIR}/TundraDBConfigVersion.cmake" + VERSION ${PROJECT_VERSION} + COMPATIBILITY SameMajorVersion +) + +# Export the targets +install(EXPORT TundraDBTargets + FILE TundraDBTargets.cmake + NAMESPACE TundraDB:: + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/TundraDB +) + +# Generate and install the config file +configure_package_config_file( + "${CMAKE_CURRENT_SOURCE_DIR}/cmake/TundraDBConfig.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/TundraDBConfig.cmake" + INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/TundraDB +) + +install(FILES + "${CMAKE_CURRENT_BINARY_DIR}/TundraDBConfig.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/TundraDBConfigVersion.cmake" + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/TundraDB ) -# Enable testing -enable_testing() +# --------------------------------------------------------------------------- +# CPack — generate distributable packages (.tar.gz, .deb, .rpm, .dmg) +# --------------------------------------------------------------------------- +set(CPACK_PACKAGE_NAME "TundraDB") +set(CPACK_PACKAGE_VERSION ${PROJECT_VERSION}) +set(CPACK_PACKAGE_VENDOR "TundraDB") +set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Graph database with temporal versioning, per-schema IDs, and Apache Arrow") +set(CPACK_PACKAGE_HOMEPAGE_URL "https://github.com/yourusername/tundradb") +if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE") + set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE") +endif() +set(CPACK_PACKAGE_FILE_NAME "TundraDB-${PROJECT_VERSION}-${CMAKE_SYSTEM_NAME}-${CMAKE_SYSTEM_PROCESSOR}") + +# Archive generators (works everywhere) +set(CPACK_GENERATOR "TGZ") + +# macOS: also generate .dmg +if(APPLE) + list(APPEND CPACK_GENERATOR "DragNDrop") +endif() + +# Linux: generate .deb and .rpm +if(CMAKE_SYSTEM_NAME STREQUAL "Linux") + # Debian/Ubuntu .deb + list(APPEND CPACK_GENERATOR "DEB") + set(CPACK_DEBIAN_PACKAGE_MAINTAINER "TundraDB Maintainers") + set(CPACK_DEBIAN_PACKAGE_DEPENDS "libarrow-dev, libparquet-dev, libtbb-dev, libspdlog-dev") + set(CPACK_DEBIAN_PACKAGE_SECTION "database") + + # Fedora/RHEL .rpm + list(APPEND CPACK_GENERATOR "RPM") + set(CPACK_RPM_PACKAGE_LICENSE "MIT") + set(CPACK_RPM_PACKAGE_GROUP "Development/Libraries") + set(CPACK_RPM_PACKAGE_REQUIRES "arrow-devel, parquet-devel, tbb-devel, spdlog-devel") +endif() + +# Source package +set(CPACK_SOURCE_GENERATOR "TGZ") +set(CPACK_SOURCE_IGNORE_FILES + "/build/" + "/build_release/" + "/build_tsan/" + "/dist/" + "/\\\\.git/" + "/test_db_" + "/tundra_profile" + "\\\\.trace/" +) -# Add tests directory -add_subdirectory(tests) +include(CPack) diff --git a/cmake/TundraDBConfig.cmake.in b/cmake/TundraDBConfig.cmake.in new file mode 100644 index 0000000..38b6f17 --- /dev/null +++ b/cmake/TundraDBConfig.cmake.in @@ -0,0 +1,35 @@ +@PACKAGE_INIT@ + +# TundraDB CMake package configuration +# +# Usage: +# find_package(TundraDB REQUIRED) +# target_link_libraries(myapp PRIVATE TundraDB::tundradb) +# +# This will automatically set up include paths and link the library. + +include(CMakeFindDependencyMacro) + +# TundraDB's public API depends on these packages. +# They must be available on the consumer's machine. +find_dependency(Arrow REQUIRED) +find_dependency(Parquet REQUIRED) +find_dependency(TBB REQUIRED) +find_dependency(spdlog REQUIRED) + +# Try to find optional Arrow components +find_dependency(ArrowCompute QUIET) +find_dependency(ArrowDataset QUIET) + +# Find LLVM (needed for DenseSet/DenseMap in public headers) +if(APPLE) + find_dependency(LLVM REQUIRED CONFIG PATHS /opt/homebrew/opt/llvm/lib/cmake/llvm) +else() + find_dependency(LLVM REQUIRED) +endif() + +# Import the exported targets +include("${CMAKE_CURRENT_LIST_DIR}/TundraDBTargets.cmake") + +check_required_components(TundraDB) + diff --git a/docs/release-macos.md b/docs/release-macos.md new file mode 100644 index 0000000..831b225 --- /dev/null +++ b/docs/release-macos.md @@ -0,0 +1,242 @@ +# TundraDB — macOS Release Process + +## Overview + +This document describes how to build and package TundraDB for macOS distribution. +The process produces a self-contained `.tar.gz` archive containing `tundra_shell` +and all its dynamic library dependencies — no Homebrew or other installs required +on the target machine. + +### Output structure + +``` +TundraDB-1.0.0-macOS-arm64.tar.gz +└── TundraDB-1.0.0-macOS/ + ├── tundra_shell ← launcher script (run this) + ├── bin/ + │ └── tundra_shell ← binary (library paths rewritten) + ├── libs/ + │ └── *.dylib ← all bundled dependencies + └── VERSION +``` + +--- + +## Prerequisites + +Install these on the build machine (the machine where you create the release): + +```bash +# Build tools +brew install cmake + +# Library bundler — copies dylibs and rewrites load paths +brew install dylibbundler +``` + +All TundraDB dependencies (Arrow, Parquet, TBB, ANTLR, LLVM, etc.) must already +be installed. If you can build TundraDB from source, you're good. + +--- + +## Quick Release (one command) + +```bash +./scripts/create_macos_bundle.sh +``` + +This will: + +1. Configure a **Release** build in `build_release/` +2. Build `tundra_shell` +3. Copy the binary to `dist/TundraDB--macOS/bin/` +4. Run `dylibbundler` to collect all `.dylib` dependencies into `libs/` + and rewrite the binary's load paths to `@executable_path/../libs/` +5. Create a launcher script +6. Verify all references are resolved +7. Create `dist/TundraDB--macOS-.tar.gz` + +### Options + +| Flag | Effect | +|-----------------|---------------------------------------------| +| `--skip-build` | Skip cmake configure/build, reuse existing | + +### Examples + +```bash +# Full build + package +./scripts/create_macos_bundle.sh + +# Just re-package (binary already built in build_release/) +./scripts/create_macos_bundle.sh --skip-build +``` + +--- + +## Step-by-Step (manual) + +If you prefer to run the steps individually: + +### 1. Configure Release build + +```bash +cmake -B build_release -S . \ + -DCMAKE_BUILD_TYPE=Release \ + -DTUNDRADB_BUILD_TESTS=OFF \ + -DTUNDRADB_BUILD_BENCHMARKS=OFF +``` + +### 2. Build + +```bash +cmake --build build_release -j$(sysctl -n hw.ncpu) +``` + +### 3. Prepare bundle directory + +```bash +mkdir -p dist/TundraDB-1.0.0-macOS/bin +mkdir -p dist/TundraDB-1.0.0-macOS/libs +cp build_release/tundra_shell dist/TundraDB-1.0.0-macOS/bin/ +``` + +### 4. Bundle dynamic libraries + +```bash +dylibbundler -od -b \ + -x dist/TundraDB-1.0.0-macOS/bin/tundra_shell \ + -d dist/TundraDB-1.0.0-macOS/libs/ \ + -p @executable_path/../libs/ \ + -s /usr/local/lib \ + -s /opt/homebrew/lib \ + -s /opt/homebrew/opt/llvm/lib +``` + +**What this does:** +- `-od` — overwrite existing files in the destination +- `-b` — bundle (copy) libraries +- `-x` — the executable to process +- `-d` — where to copy the `.dylib` files +- `-p` — prefix to rewrite into the binary (`@executable_path/../libs/`) +- `-s` — extra directories to search for `@rpath` libraries + +If `dylibbundler` prompts for a library path, it means an `@rpath` reference +couldn't be found in the search paths. Add the missing directory with another +`-s /path/to/dir` flag. + +### 5. Verify + +```bash +# Should show only /usr/lib, /System, and @executable_path references +otool -L dist/TundraDB-1.0.0-macOS/bin/tundra_shell + +# Smoke test +dist/TundraDB-1.0.0-macOS/bin/tundra_shell --help +``` + +### 6. Create archive + +```bash +cd dist +tar -czf TundraDB-1.0.0-macOS-arm64.tar.gz TundraDB-1.0.0-macOS +``` + +--- + +## How It Works + +### The problem + +macOS binaries reference their dynamic libraries by path. A development build +points to absolute Homebrew paths like `/opt/homebrew/lib/libarrow.dylib`. +This won't work on a machine without Homebrew. + +### The solution + +`dylibbundler` does two things: + +1. **Copies** every non-system `.dylib` (and their transitive dependencies) + into the bundle's `libs/` directory. +2. **Rewrites** every library reference in the binary (using `install_name_tool`) + from absolute paths to `@executable_path/../libs/`. + +This makes the binary fully self-contained. At runtime, macOS resolves +`@executable_path` to the directory containing the binary (`bin/`), then +looks for libs in `../libs/` relative to that. + +### Directory layout requirement + +The binary expects libs at `../libs/` relative to itself, so the structure +**must** be: + +``` +/ +├── bin/ +│ └── tundra_shell ← binary lives here +└── libs/ + └── *.dylib ← @executable_path/../libs/ resolves here +``` + +--- + +## Folder Structure + +| Directory | Purpose | In `.gitignore` | +|------------------|--------------------------------|-----------------| +| `build_release/` | CMake build artifacts | ✅ Yes | +| `dist/` | Final distributable packages | ✅ Yes | +| `build/` | Development (Debug) build | ✅ Yes | + +`build_release/` contains CMake cache, object files, and intermediate build +artifacts — these are **not** distributable. The clean, distributable output +goes into `dist/`. + +--- + +## Troubleshooting + +### `dylibbundler` prompts for a library path + +``` +/!\ WARNING : can't get path for '@rpath/libfoo.dylib' +Please specify the directory where this library is located: +``` + +Find the library and add its directory as a search path: + +```bash +find /opt/homebrew /usr/local -name "libfoo*" -type f 2>/dev/null +# Then re-run with: -s /path/to/directory +``` + +### Binary crashes with "Library not loaded" + +Check that the directory layout is correct — `libs/` must be a sibling of +`bin/`: + +```bash +otool -L bin/tundra_shell | head -5 +# Should show @executable_path/../libs/... +``` + +### Archive too large + +The bundle includes ~100 dylibs (~20 MB compressed). To reduce size: + +```bash +# Strip debug symbols from bundled libs +strip -x dist/TundraDB-1.0.0-macOS/libs/*.dylib +``` + +--- + +## Platform Notes + +- The archive is **architecture-specific**: an `arm64` build won't run on + Intel Macs (and vice versa). The script names the archive accordingly. +- To support both architectures, build on each platform separately or + create a Universal Binary with `CMAKE_OSX_ARCHITECTURES="arm64;x86_64"` + (requires all dependencies to also be universal). +- This archive is **macOS only**. For Linux, see the Linux release process. + diff --git a/include/core.hpp b/include/core.hpp index dbf1bc3..9822c12 100644 --- a/include/core.hpp +++ b/include/core.hpp @@ -18,7 +18,7 @@ #include #include -#include "../libs/json/json.hpp" +#include "json.hpp" #include "arrow_utils.hpp" #include "config.hpp" #include "edge_store.hpp" diff --git a/include/file_utils.hpp b/include/file_utils.hpp index 0df6724..3fa9ef3 100644 --- a/include/file_utils.hpp +++ b/include/file_utils.hpp @@ -7,7 +7,7 @@ #include #include -#include "../libs/json/json.hpp" +#include "json.hpp" namespace tundradb { diff --git a/include/metadata.hpp b/include/metadata.hpp index f389a3e..2981f2f 100644 --- a/include/metadata.hpp +++ b/include/metadata.hpp @@ -8,7 +8,7 @@ #include #include -#include "../libs/json/json.hpp" +#include "json.hpp" #include "file_utils.hpp" #include "llvm/ADT/SmallVector.h" #include "logger.hpp" diff --git a/scripts/create_macos_bundle.sh b/scripts/create_macos_bundle.sh index 86d90d8..909626c 100755 --- a/scripts/create_macos_bundle.sh +++ b/scripts/create_macos_bundle.sh @@ -1,138 +1,157 @@ #!/bin/bash +set -euo pipefail +# --------------------------------------------------------------------------- # TundraDB macOS Bundle Creator -# Uses environment variables for library loading (no binary modification) -set -e +# +# Produces a self-contained distributable archive for macOS. +# Uses dylibbundler to collect and rewrite all dynamic library references. +# +# Prerequisites: +# brew install dylibbundler +# +# Usage: +# ./scripts/create_macos_bundle.sh # defaults +# ./scripts/create_macos_bundle.sh --skip-build # reuse existing build +# --------------------------------------------------------------------------- -# Configuration -BUNDLE_NAME="TundraDB" VERSION="1.0.0" -BUILD_DIR="build" -BUNDLE_DIR="dist/${BUNDLE_NAME}-${VERSION}-macOS" +BUNDLE_NAME="TundraDB" APP_NAME="tundra_shell" -echo "Creating TundraDB macOS bundle..." +SKIP_BUILD=false +for arg in "$@"; do + case "$arg" in + --skip-build) SKIP_BUILD=true ;; + esac +done -# Clean and create bundle directory -rm -rf dist -mkdir -p "${BUNDLE_DIR}/bin" -mkdir -p "${BUNDLE_DIR}/lib" +# Paths (all relative to project root) +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)" +BUILD_DIR="${PROJECT_DIR}/build_release" +DIST_DIR="${PROJECT_DIR}/dist" +BUNDLE_DIR="${DIST_DIR}/${BUNDLE_NAME}-${VERSION}-macOS" +ARCH="$(uname -m)" # arm64 or x86_64 + +echo "=== TundraDB macOS Bundle Creator ===" +echo "Version : ${VERSION}" +echo "Arch : ${ARCH}" +echo "Build : ${BUILD_DIR}" +echo "Output : ${BUNDLE_DIR}" +echo "" + +# --------------------------------------------------------------------------- +# 1. Check prerequisites +# --------------------------------------------------------------------------- +if ! command -v dylibbundler &>/dev/null; then + echo "❌ dylibbundler not found. Install it with:" + echo " brew install dylibbundler" + exit 1 +fi + +if ! command -v cmake &>/dev/null; then + echo "❌ cmake not found. Install it with:" + echo " brew install cmake" + exit 1 +fi + +# --------------------------------------------------------------------------- +# 2. Build (Release) +# --------------------------------------------------------------------------- +if [ "$SKIP_BUILD" = false ]; then + echo ">>> Configuring (Release)..." + cmake -B "${BUILD_DIR}" -S "${PROJECT_DIR}" \ + -DCMAKE_BUILD_TYPE=Release \ + -DTUNDRADB_BUILD_TESTS=OFF \ + -DTUNDRADB_BUILD_BENCHMARKS=OFF + + echo "" + echo ">>> Building..." + cmake --build "${BUILD_DIR}" -j"$(sysctl -n hw.ncpu)" + echo "" +fi -# Build the project if not already built if [ ! -f "${BUILD_DIR}/${APP_NAME}" ]; then - echo "Building TundraDB..." - mkdir -p ${BUILD_DIR} - cd ${BUILD_DIR} - cmake .. -DCMAKE_BUILD_TYPE=Release - make -j$(sysctl -n hw.ncpu) - cd .. + echo "❌ Binary not found at ${BUILD_DIR}/${APP_NAME}" + echo " Run without --skip-build or build manually first." + exit 1 fi -# Copy the main executable without modification +# --------------------------------------------------------------------------- +# 3. Prepare bundle directory +# --------------------------------------------------------------------------- +rm -rf "${BUNDLE_DIR}" +mkdir -p "${BUNDLE_DIR}/bin" +mkdir -p "${BUNDLE_DIR}/libs" + +# Copy the binary (we need a fresh copy; dylibbundler modifies it in place) cp "${BUILD_DIR}/${APP_NAME}" "${BUNDLE_DIR}/bin/" +chmod +w "${BUNDLE_DIR}/bin/${APP_NAME}" + +echo ">>> Running dylibbundler..." +dylibbundler -od -b \ + -x "${BUNDLE_DIR}/bin/${APP_NAME}" \ + -d "${BUNDLE_DIR}/libs/" \ + -p @executable_path/../libs/ \ + -s /usr/local/lib \ + -s /opt/homebrew/lib \ + -s /opt/homebrew/opt/llvm/lib -# Copy all required libraries without modifying them -echo "Copying dependencies..." - -# Function to copy libraries recursively -copy_dependencies() { - local binary="$1" - local lib_dir="$2" - - # Get list of dynamic libraries - otool -L "$binary" | grep -E "(homebrew|local|@rpath)" | awk '{print $1}' | while read lib; do - # Handle @rpath libraries - if [[ "$lib" == @rpath/* ]]; then - lib_name=$(basename "$lib") - # Try to find the actual library in common locations - actual_lib="" - for search_path in /opt/homebrew/lib /usr/local/lib; do - if [ -f "${search_path}/${lib_name}" ]; then - actual_lib="${search_path}/${lib_name}" - break - fi - done - - if [ -n "$actual_lib" ] && [ -f "$actual_lib" ]; then - if [ ! -f "${lib_dir}/${lib_name}" ]; then - echo " Copying $lib_name (from @rpath)" - cp "$actual_lib" "${lib_dir}/" - chmod +w "${lib_dir}/${lib_name}" - - # Copy dependencies of this library recursively - copy_dependencies "${lib_dir}/${lib_name}" "$lib_dir" - fi - fi - elif [ -f "$lib" ]; then - # Handle regular library paths - lib_name=$(basename "$lib") - if [ ! -f "${lib_dir}/${lib_name}" ]; then - echo " Copying $lib_name" - cp "$lib" "${lib_dir}/" - chmod +w "${lib_dir}/${lib_name}" - - # Copy dependencies of this library recursively - copy_dependencies "${lib_dir}/${lib_name}" "$lib_dir" - fi - fi - done -} - -# Copy dependencies for the main executable -copy_dependencies "${BUNDLE_DIR}/bin/${APP_NAME}" "${BUNDLE_DIR}/lib" - -# Create a launcher script that sets up the environment +echo "" + +# --------------------------------------------------------------------------- +# 4. Create launcher script +# --------------------------------------------------------------------------- cat > "${BUNDLE_DIR}/tundra_shell" << 'EOF' #!/bin/bash -# TundraDB Launcher Script - -# Get the directory where this script is located SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" - -# Set up library paths for both Homebrew locations and our bundled libs -export DYLD_LIBRARY_PATH="${SCRIPT_DIR}/lib:/opt/homebrew/lib:/usr/local/lib:${DYLD_LIBRARY_PATH}" -export DYLD_FALLBACK_LIBRARY_PATH="${SCRIPT_DIR}/lib:/opt/homebrew/lib:/usr/local/lib" - -# Run the application exec "${SCRIPT_DIR}/bin/tundra_shell" "$@" EOF - chmod +x "${BUNDLE_DIR}/tundra_shell" -# Create README -cat > "${BUNDLE_DIR}/README.md" << EOF -# TundraDB v${VERSION} for macOS - -## Installation -1. Extract this archive to any location -2. Run \`./tundra_shell\` from the extracted directory - -## Usage -\`\`\`bash -./tundra_shell --help -./tundra_shell --db-path /path/to/your/database -\`\`\` +# --------------------------------------------------------------------------- +# 5. Metadata +# --------------------------------------------------------------------------- +echo "${VERSION}" > "${BUNDLE_DIR}/VERSION" -## Requirements -- macOS 10.15 or later -- This bundle includes most dependencies, but may require Homebrew libraries to be installed +# --------------------------------------------------------------------------- +# 6. Verify +# --------------------------------------------------------------------------- +echo ">>> Verifying binary..." +UNRESOLVED=$(otool -L "${BUNDLE_DIR}/bin/${APP_NAME}" \ + | tail -n +2 \ + | awk '{print $1}' \ + | grep -vE '^(/usr/lib/|/System/|@executable_path|@rpath)' || true) + +if [ -n "$UNRESOLVED" ]; then + echo "⚠️ Warning — these references are not bundled:" + echo "$UNRESOLVED" +else + echo "✅ All non-system libraries are bundled" +fi -## Support -For issues and documentation, visit: https://github.com/yourusername/tundradb -EOF +# Quick smoke test +if "${BUNDLE_DIR}/tundra_shell" --help &>/dev/null; then + echo "✅ Smoke test passed (--help)" +else + echo "⚠️ Smoke test failed — the binary may not run correctly" +fi -# Create version info -echo "${VERSION}" > "${BUNDLE_DIR}/VERSION" +# --------------------------------------------------------------------------- +# 7. Create archive +# --------------------------------------------------------------------------- +ARCHIVE_NAME="${BUNDLE_NAME}-${VERSION}-macOS-${ARCH}.tar.gz" +cd "${DIST_DIR}" +tar -czf "${ARCHIVE_NAME}" "$(basename "${BUNDLE_DIR}")" -# Create the final archive -cd dist -tar -czf "${BUNDLE_NAME}-${VERSION}-macOS.tar.gz" "${BUNDLE_NAME}-${VERSION}-macOS" -cd .. +ARCHIVE_SIZE=$(du -sh "${ARCHIVE_NAME}" | awk '{print $1}') -echo "✅ Bundle created successfully!" -echo "📦 Location: dist/${BUNDLE_NAME}-${VERSION}-macOS.tar.gz" -echo "📁 Directory: ${BUNDLE_DIR}" echo "" -echo "To test the bundle:" -echo " cd ${BUNDLE_DIR}" -echo " ./tundra_shell --help" \ No newline at end of file +echo "=== Done ===" +echo "📦 Archive : dist/${ARCHIVE_NAME} (${ARCHIVE_SIZE})" +echo "📁 Directory: dist/$(basename "${BUNDLE_DIR}")/" +echo "" +echo "To test:" +echo " cd dist/$(basename "${BUNDLE_DIR}")" +echo " ./tundra_shell --help" diff --git a/src/metadata.cpp b/src/metadata.cpp index a956065..f49cc02 100644 --- a/src/metadata.cpp +++ b/src/metadata.cpp @@ -6,7 +6,7 @@ #include #include -#include "../libs/json/json.hpp" +#include "json.hpp" #include "logger.hpp" #include "utils.hpp" diff --git a/src/storage.cpp b/src/storage.cpp index cfbe06a..e631abe 100644 --- a/src/storage.cpp +++ b/src/storage.cpp @@ -16,7 +16,7 @@ #include #include -#include "../libs/json/json.hpp" +#include "json.hpp" #include "logger.hpp" #include "metadata.hpp" #include "table_info.hpp" diff --git a/src/tundra_shell.cpp b/src/tundra_shell.cpp index 4b78999..0ef45f8 100644 --- a/src/tundra_shell.cpp +++ b/src/tundra_shell.cpp @@ -1,7 +1,7 @@ #include // Include this first to define EOF // First include json before antlr to prevent conflicts -#include "../libs/json/json.hpp" +#include "json.hpp" // Now include Arrow libraries #include