diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 000000000..2a7305bcb --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,762 @@ +name: Build TablePro + +on: + push: + branches: [main] + tags: ['v*'] + paths-ignore: + - '**.md' + - 'docs/**' + - '.vscode/**' + pull_request: + branches: [main] + paths-ignore: + - '**.md' + - 'docs/**' + +env: + XCODE_PROJECT: TablePro.xcodeproj + XCODE_SCHEME: TablePro + BUILD_CONFIGURATION: Release + +jobs: + lint: + name: SwiftLint + runs-on: macos-14 + timeout-minutes: 10 + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Cache SwiftLint + uses: actions/cache@v4 + with: + path: ~/Library/Caches/Homebrew/swiftlint* + key: swiftlint-${{ runner.os }}-${{ hashFiles('.swiftlint.yml') }} + restore-keys: | + swiftlint-${{ runner.os }}- + + - name: Install SwiftLint + run: brew install swiftlint + + - name: Run SwiftLint + run: swiftlint lint --strict + + prepare-libs: + name: Prepare Universal Libraries + runs-on: macos-14 + needs: lint + timeout-minutes: 25 + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Cache Homebrew packages + uses: actions/cache@v4 + with: + path: | + ~/Library/Caches/Homebrew + /opt/homebrew/Cellar/mariadb-connector-c + /opt/homebrew/Cellar/libpq + /usr/local/Cellar/mariadb-connector-c + /usr/local/Cellar/libpq + key: brew-deps-${{ runner.os }}-${{ hashFiles('**/build-release.sh') }} + restore-keys: | + brew-deps-${{ runner.os }}- + + - name: Install ARM64 Homebrew dependencies + run: | + echo "Installing ARM64 dependencies..." + brew install mariadb-connector-c libpq + + # Verify installations + if ! brew list mariadb-connector-c >/dev/null 2>&1; then + echo "❌ ERROR: mariadb-connector-c installation failed or incomplete" + exit 1 + fi + + if ! brew list libpq >/dev/null 2>&1; then + echo "❌ ERROR: libpq installation failed or incomplete" + exit 1 + fi + + # Get the actual prefix for mariadb-connector-c (handles keg-only formulas) + MARIADB_PREFIX=$(brew --prefix mariadb-connector-c) + echo "MariaDB Connector/C prefix: $MARIADB_PREFIX" + + # Find the library file (may be in lib/ or lib/mariadb/) + MARIADB_LIB="" + for path in "$MARIADB_PREFIX/lib/mariadb/libmariadb.a" "$MARIADB_PREFIX/lib/libmariadb.a"; do + if [ -f "$path" ]; then + MARIADB_LIB="$path" + break + fi + done + + if [ -z "$MARIADB_LIB" ]; then + echo "❌ ERROR: mariadb-connector-c installed but library file not found" + echo "Searched in:" + echo " - $MARIADB_PREFIX/lib/mariadb/libmariadb.a" + echo " - $MARIADB_PREFIX/lib/libmariadb.a" + echo "" + echo "Contents of $MARIADB_PREFIX/lib/:" + ls -la "$MARIADB_PREFIX/lib/" || echo "Directory not found" + exit 1 + fi + + echo "✅ Found ARM64 MariaDB library at: $MARIADB_LIB" + echo "ARM64_MARIADB_LIB=$MARIADB_LIB" >> $GITHUB_ENV + echo "✅ All ARM64 dependencies installed successfully" + + - name: Install x86_64 Homebrew + run: | + # Install Rosetta 2 if needed + if ! arch -x86_64 /usr/bin/true 2>/dev/null; then + echo "Installing Rosetta 2..." + if ! softwareupdate --install-rosetta --agree-to-license; then + echo "❌ ERROR: Failed to install Rosetta 2" + echo "This is required to run x86_64 binaries on Apple Silicon" + exit 1 + fi + + # Verify Rosetta 2 is now working + if ! arch -x86_64 /usr/bin/true 2>/dev/null; then + echo "❌ ERROR: Rosetta 2 installed but not functional" + exit 1 + fi + + echo "✅ Rosetta 2 installed successfully" + else + echo "✅ Rosetta 2 already installed" + fi + + # Check if x86_64 Homebrew is already installed + if [ ! -f /usr/local/bin/brew ]; then + echo "Installing x86_64 Homebrew..." + + # Using official Homebrew installer (trusted source) + if ! arch -x86_64 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"; then + echo "❌ ERROR: Homebrew installation script failed" + exit 1 + fi + + # Verify installation + if [ ! -f /usr/local/bin/brew ]; then + echo "❌ ERROR: Homebrew installed but /usr/local/bin/brew not found" + exit 1 + fi + + # Verify it's executable + if ! /usr/local/bin/brew --version; then + echo "❌ ERROR: Homebrew binary is not functional" + exit 1 + fi + + echo "✅ x86_64 Homebrew installed successfully" + else + echo "x86_64 Homebrew already installed" + + # Still verify it works + if ! /usr/local/bin/brew --version; then + echo "❌ ERROR: x86_64 Homebrew exists but is not functional" + exit 1 + fi + fi + + - name: Install x86_64 Homebrew dependencies + run: | + echo "Installing x86_64 dependencies..." + arch -x86_64 /usr/local/bin/brew install mariadb-connector-c libpq + + # Verify installations + if ! arch -x86_64 /usr/local/bin/brew list mariadb-connector-c >/dev/null 2>&1; then + echo "❌ ERROR: mariadb-connector-c installation failed or incomplete" + exit 1 + fi + + if ! arch -x86_64 /usr/local/bin/brew list libpq >/dev/null 2>&1; then + echo "❌ ERROR: libpq installation failed or incomplete" + exit 1 + fi + + # Get the actual prefix for mariadb-connector-c (handles keg-only formulas) + X86_MARIADB_PREFIX=$(arch -x86_64 /usr/local/bin/brew --prefix mariadb-connector-c) + echo "x86_64 MariaDB Connector/C prefix: $X86_MARIADB_PREFIX" + + # Find the library file (may be in lib/ or lib/mariadb/) + X86_MARIADB_LIB="" + for path in "$X86_MARIADB_PREFIX/lib/mariadb/libmariadb.a" "$X86_MARIADB_PREFIX/lib/libmariadb.a"; do + if [ -f "$path" ]; then + X86_MARIADB_LIB="$path" + break + fi + done + + if [ -z "$X86_MARIADB_LIB" ]; then + echo "❌ ERROR: mariadb-connector-c installed but library file not found" + echo "Searched in:" + echo " - $X86_MARIADB_PREFIX/lib/mariadb/libmariadb.a" + echo " - $X86_MARIADB_PREFIX/lib/libmariadb.a" + echo "" + echo "Contents of $X86_MARIADB_PREFIX/lib/:" + ls -la "$X86_MARIADB_PREFIX/lib/" || echo "Directory not found" + exit 1 + fi + + echo "✅ Found x86_64 MariaDB library at: $X86_MARIADB_LIB" + echo "X86_MARIADB_LIB=$X86_MARIADB_LIB" >> $GITHUB_ENV + echo "✅ All x86_64 dependencies installed successfully" + + - name: Create universal libmariadb library + run: | + echo "Creating universal libmariadb.a..." + + # Use library paths from environment (set by previous steps) + ARM64_LIB="${ARM64_MARIADB_LIB}" + X86_LIB="${X86_MARIADB_LIB}" + + echo "ARM64 library path: $ARM64_LIB" + echo "x86_64 library path: $X86_LIB" + + # Verify input files exist + if [ -z "$ARM64_LIB" ] || [ ! -f "$ARM64_LIB" ]; then + echo "❌ ERROR: ARM64 library not found at: $ARM64_LIB" + echo "ARM64 Homebrew installation may have failed" + exit 1 + fi + + if [ -z "$X86_LIB" ] || [ ! -f "$X86_LIB" ]; then + echo "❌ ERROR: x86_64 library not found at: $X86_LIB" + echo "x86_64 Homebrew installation may have failed" + exit 1 + fi + + # Verify inputs are correct architectures + echo "Verifying input architectures:" + lipo -info "$ARM64_LIB" + lipo -info "$X86_LIB" + + # Ensure output directory exists + mkdir -p Libs + + # Create universal binary + if ! lipo -create "$ARM64_LIB" "$X86_LIB" -output Libs/libmariadb_universal.a; then + echo "❌ ERROR: Failed to create universal library" + echo "This could mean:" + echo " - Libraries are incompatible" + echo " - Libraries are already universal" + echo " - Output directory is not writable" + exit 1 + fi + + # Verify output + if [ ! -f "Libs/libmariadb_universal.a" ]; then + echo "❌ ERROR: Universal library was not created" + exit 1 + fi + + echo "✅ Universal library created successfully:" + lipo -info Libs/libmariadb_universal.a + ls -lh Libs/libmariadb_universal.a + + # Verify it's actually universal (contains both architectures) + if ! lipo -info Libs/libmariadb_universal.a | grep -q "arm64"; then + echo "❌ ERROR: Universal library missing arm64 architecture" + exit 1 + fi + + if ! lipo -info Libs/libmariadb_universal.a | grep -q "x86_64"; then + echo "❌ ERROR: Universal library missing x86_64 architecture" + exit 1 + fi + + echo "✅ Verified: Library contains both arm64 and x86_64" + + - name: Upload library artifacts + uses: actions/upload-artifact@v4 + with: + name: libs-universal + path: Libs/ + retention-days: 1 + + build-arm64: + name: Build ARM64 + runs-on: macos-14 + needs: prepare-libs + timeout-minutes: 20 + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Download library artifacts + uses: actions/download-artifact@v4 + with: + name: libs-universal + path: Libs/ + + - name: Verify downloaded artifacts + run: | + echo "Verifying downloaded library artifacts..." + + if [ ! -f "Libs/libmariadb_universal.a" ]; then + echo "❌ ERROR: Required library artifact not found: Libs/libmariadb_universal.a" + echo "This means:" + echo " - The prepare-libs job failed to create the library" + echo " - The artifact upload failed" + echo " - The artifact download was corrupted" + echo "" + echo "Contents of Libs directory:" + ls -la Libs/ || echo "Libs directory is empty or doesn't exist" + exit 1 + fi + + echo "✅ Universal library found" + lipo -info Libs/libmariadb_universal.a + ls -lh Libs/libmariadb_universal.a + + - name: Cache Homebrew packages + uses: actions/cache@v4 + with: + path: | + ~/Library/Caches/Homebrew + /opt/homebrew/Cellar/mariadb-connector-c + /opt/homebrew/Cellar/libpq + key: brew-arm64-${{ runner.os }}-${{ hashFiles('**/build-release.sh') }} + restore-keys: | + brew-arm64-${{ runner.os }}- + + - name: Install ARM64 dependencies + run: | + echo "Installing ARM64 dependencies..." + brew install mariadb-connector-c libpq + + # Verify installations + if ! brew list mariadb-connector-c >/dev/null 2>&1; then + echo "❌ ERROR: mariadb-connector-c installation failed" + exit 1 + fi + + if ! brew list libpq >/dev/null 2>&1; then + echo "❌ ERROR: libpq installation failed" + exit 1 + fi + + echo "✅ ARM64 dependencies installed" + + - name: Build ARM64 + run: | + chmod +x build-release.sh + ./build-release.sh arm64 + + - name: Verify build + run: | + echo "Verifying build output..." + + BINARY_PATH="build/Release/TablePro-arm64.app/Contents/MacOS/TablePro" + + # Check binary exists + if [ ! -f "$BINARY_PATH" ]; then + echo "❌ ERROR: Built binary not found at: $BINARY_PATH" + echo "Build may have failed silently" + exit 1 + fi + + # Check it's not empty + if [ ! -s "$BINARY_PATH" ]; then + echo "❌ ERROR: Binary file is empty" + exit 1 + fi + + # Check architecture + ARCH_INFO=$(lipo -info "$BINARY_PATH") + echo "Architecture: $ARCH_INFO" + + if ! echo "$ARCH_INFO" | grep -q "arm64"; then + echo "❌ ERROR: Binary does not contain arm64 architecture" + echo "Expected: arm64 only" + echo "Got: $ARCH_INFO" + exit 1 + fi + + if echo "$ARCH_INFO" | grep -q "x86_64"; then + echo "❌ ERROR: Binary contains x86_64 but should be arm64 only" + exit 1 + fi + + # Check it's executable + if [ ! -x "$BINARY_PATH" ]; then + echo "❌ ERROR: Binary is not executable" + exit 1 + fi + + # Display info + echo "✅ Build verified successfully" + echo "Binary size: $(ls -lh "$BINARY_PATH" | awk '{print $5}')" + echo "App bundle size: $(du -sh build/Release/TablePro-arm64.app | awk '{print $1}')" + + - name: Create ZIP archive + run: | + echo "Creating ZIP archive..." + + if [ ! -d "build/Release/TablePro-arm64.app" ]; then + echo "❌ ERROR: App bundle not found for archiving" + exit 1 + fi + + cd build/Release + + if ! zip -r TablePro-arm64.zip TablePro-arm64.app; then + echo "❌ ERROR: Failed to create ZIP archive" + exit 1 + fi + + if [ ! -f "TablePro-arm64.zip" ]; then + echo "❌ ERROR: ZIP file was not created" + exit 1 + fi + + # Verify it's a valid ZIP + if ! unzip -t TablePro-arm64.zip > /dev/null; then + echo "❌ ERROR: Created ZIP is corrupted or invalid" + exit 1 + fi + + echo "✅ ZIP archive created and verified" + ls -lh TablePro-arm64.zip + + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: TablePro-arm64-${{ github.sha }} + path: build/Release/TablePro-arm64.zip + retention-days: ${{ startsWith(github.ref, 'refs/tags/v') && 90 || 7 }} + + build-x86_64: + name: Build x86_64 + runs-on: macos-14 + needs: prepare-libs + timeout-minutes: 20 + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Download library artifacts + uses: actions/download-artifact@v4 + with: + name: libs-universal + path: Libs/ + + - name: Verify downloaded artifacts + run: | + echo "Verifying downloaded library artifacts..." + + if [ ! -f "Libs/libmariadb_universal.a" ]; then + echo "❌ ERROR: Required library artifact not found: Libs/libmariadb_universal.a" + echo "" + echo "Contents of Libs directory:" + ls -la Libs/ || echo "Libs directory is empty or doesn't exist" + exit 1 + fi + + echo "✅ Universal library found" + lipo -info Libs/libmariadb_universal.a + + - name: Cache Homebrew packages + uses: actions/cache@v4 + with: + path: | + ~/Library/Caches/Homebrew + /usr/local/Cellar/mariadb-connector-c + /usr/local/Cellar/libpq + key: brew-x86_64-${{ runner.os }}-${{ hashFiles('**/build-release.sh') }} + restore-keys: | + brew-x86_64-${{ runner.os }}- + + - name: Install Rosetta 2 + run: | + if ! arch -x86_64 /usr/bin/true 2>/dev/null; then + echo "Installing Rosetta 2..." + if ! softwareupdate --install-rosetta --agree-to-license; then + echo "❌ ERROR: Failed to install Rosetta 2" + exit 1 + fi + + # Verify Rosetta 2 works + if ! arch -x86_64 /usr/bin/true 2>/dev/null; then + echo "❌ ERROR: Rosetta 2 installed but not functional" + exit 1 + fi + + echo "✅ Rosetta 2 installed" + else + echo "✅ Rosetta 2 already installed" + fi + + - name: Install x86_64 Homebrew + run: | + if [ ! -f /usr/local/bin/brew ]; then + echo "Installing x86_64 Homebrew..." + + if ! arch -x86_64 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"; then + echo "❌ ERROR: Homebrew installation failed" + exit 1 + fi + + if [ ! -f /usr/local/bin/brew ]; then + echo "❌ ERROR: Homebrew not found after installation" + exit 1 + fi + + if ! /usr/local/bin/brew --version; then + echo "❌ ERROR: Homebrew not functional" + exit 1 + fi + + echo "✅ x86_64 Homebrew installed" + else + echo "x86_64 Homebrew already installed" + + if ! /usr/local/bin/brew --version; then + echo "❌ ERROR: Homebrew not functional" + exit 1 + fi + fi + + - name: Install x86_64 dependencies + run: | + echo "Installing x86_64 dependencies..." + arch -x86_64 /usr/local/bin/brew install mariadb-connector-c libpq + + # Verify installations + if ! arch -x86_64 /usr/local/bin/brew list mariadb-connector-c >/dev/null 2>&1; then + echo "❌ ERROR: mariadb-connector-c installation failed" + exit 1 + fi + + if ! arch -x86_64 /usr/local/bin/brew list libpq >/dev/null 2>&1; then + echo "❌ ERROR: libpq installation failed" + exit 1 + fi + + echo "✅ x86_64 dependencies installed" + + - name: Build x86_64 + run: | + chmod +x build-release.sh + ./build-release.sh x86_64 + + - name: Verify build + run: | + echo "Verifying build output..." + + BINARY_PATH="build/Release/TablePro-x86_64.app/Contents/MacOS/TablePro" + + # Check binary exists + if [ ! -f "$BINARY_PATH" ]; then + echo "❌ ERROR: Built binary not found at: $BINARY_PATH" + exit 1 + fi + + # Check it's not empty + if [ ! -s "$BINARY_PATH" ]; then + echo "❌ ERROR: Binary file is empty" + exit 1 + fi + + # Check architecture + ARCH_INFO=$(lipo -info "$BINARY_PATH") + echo "Architecture: $ARCH_INFO" + + if ! echo "$ARCH_INFO" | grep -q "x86_64"; then + echo "❌ ERROR: Binary does not contain x86_64 architecture" + echo "Expected: x86_64 only" + echo "Got: $ARCH_INFO" + exit 1 + fi + + if echo "$ARCH_INFO" | grep -q "arm64"; then + echo "❌ ERROR: Binary contains arm64 but should be x86_64 only" + exit 1 + fi + + # Check it's executable + if [ ! -x "$BINARY_PATH" ]; then + echo "❌ ERROR: Binary is not executable" + exit 1 + fi + + # Display info + echo "✅ Build verified successfully" + echo "Binary size: $(ls -lh "$BINARY_PATH" | awk '{print $5}')" + echo "App bundle size: $(du -sh build/Release/TablePro-x86_64.app | awk '{print $1}')" + + - name: Create ZIP archive + run: | + echo "Creating ZIP archive..." + + if [ ! -d "build/Release/TablePro-x86_64.app" ]; then + echo "❌ ERROR: App bundle not found for archiving" + exit 1 + fi + + cd build/Release + + if ! zip -r TablePro-x86_64.zip TablePro-x86_64.app; then + echo "❌ ERROR: Failed to create ZIP archive" + exit 1 + fi + + if [ ! -f "TablePro-x86_64.zip" ]; then + echo "❌ ERROR: ZIP file was not created" + exit 1 + fi + + # Verify it's a valid ZIP + if ! unzip -t TablePro-x86_64.zip > /dev/null; then + echo "❌ ERROR: Created ZIP is corrupted or invalid" + exit 1 + fi + + echo "✅ ZIP archive created and verified" + ls -lh TablePro-x86_64.zip + + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: TablePro-x86_64-${{ github.sha }} + path: build/Release/TablePro-x86_64.zip + retention-days: ${{ startsWith(github.ref, 'refs/tags/v') && 90 || 7 }} + + release: + name: Create GitHub Release + runs-on: macos-14 + needs: [build-arm64, build-x86_64] + if: startsWith(github.ref, 'refs/tags/v') + timeout-minutes: 10 + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Download ARM64 artifact + uses: actions/download-artifact@v4 + with: + name: TablePro-arm64-${{ github.sha }} + path: artifacts/ + + - name: Download x86_64 artifact + uses: actions/download-artifact@v4 + with: + name: TablePro-x86_64-${{ github.sha }} + path: artifacts/ + + - name: Verify and rename artifacts for release + run: | + VERSION=${GITHUB_REF#refs/tags/} + + if [ -z "$VERSION" ]; then + echo "❌ ERROR: Failed to extract version from ref: $GITHUB_REF" + exit 1 + fi + + echo "Preparing artifacts for version: $VERSION" + + # Check source files exist + if [ ! -f "artifacts/TablePro-arm64.zip" ]; then + echo "❌ ERROR: ARM64 artifact not found" + echo "Contents of artifacts directory:" + ls -la artifacts/ + exit 1 + fi + + if [ ! -f "artifacts/TablePro-x86_64.zip" ]; then + echo "❌ ERROR: x86_64 artifact not found" + echo "Contents of artifacts directory:" + ls -la artifacts/ + exit 1 + fi + + # Rename with verification + if ! mv artifacts/TablePro-arm64.zip "artifacts/TablePro-${VERSION}-arm64.zip"; then + echo "❌ ERROR: Failed to rename ARM64 artifact" + exit 1 + fi + + if ! mv artifacts/TablePro-x86_64.zip "artifacts/TablePro-${VERSION}-x86_64.zip"; then + echo "❌ ERROR: Failed to rename x86_64 artifact" + exit 1 + fi + + # Verify renamed files exist + if [ ! -f "artifacts/TablePro-${VERSION}-arm64.zip" ]; then + echo "❌ ERROR: Renamed ARM64 file not found" + exit 1 + fi + + if [ ! -f "artifacts/TablePro-${VERSION}-x86_64.zip" ]; then + echo "❌ ERROR: Renamed x86_64 file not found" + exit 1 + fi + + echo "✅ Artifacts renamed successfully" + ls -lh artifacts/ + + - name: Generate release notes + id: release_notes + run: | + VERSION=${GITHUB_REF#refs/tags/} + + # Try to get previous tag + PREVIOUS_TAG=$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo "") + + if [ -z "$PREVIOUS_TAG" ]; then + echo "No previous tag found, showing recent commits" + CHANGES=$(git log --pretty=format:"- %s" HEAD~10..HEAD 2>/dev/null || echo "- Initial release") + else + echo "Generating changes since $PREVIOUS_TAG" + CHANGES=$(git log --pretty=format:"- %s" "$PREVIOUS_TAG"..HEAD 2>/dev/null || echo "- Initial release") + fi + + if [ -z "$CHANGES" ]; then + CHANGES="- Initial release" + fi + + cat > release_notes.md < **Note:** For this universal build to succeed, the MariaDB static library must also be a universal binary. +> Ensure that `Libs/libmariadb_universal.a` exists (or create it first, similar to what `build-release.sh` expects) and that your Xcode project links against this universal library instead of an architecture-specific `libmariadb.a`. +> For example, if you have separate per-architecture libraries, you can create the universal one with: +> +> ```bash +> lipo -create \ +> path/to/arm64/libmariadb.a \ +> path/to/x86_64/libmariadb.a \ +> -output Libs/libmariadb_universal.a +> ``` +**Output:** +- `TablePro.app` - Runs on both Apple Silicon and Intel (~12MB) + +**Benefits:** +- ✅ One download for all users +- ✅ Simpler distribution +- ❌ 2x file size + +## Size Comparison + +| Build Type | Size | Notes | +|------------|------|-------| +| ARM64-only | 5.9MB | Apple Silicon Macs only | +| x86_64-only | 6.0MB | Intel Macs only | +| Universal | ~12MB | Both architectures | + +## Dependencies + +### Apple Silicon Mac (ARM64) +```bash +brew install mariadb-connector-c libpq +``` + +### Intel Mac (x86_64) +```bash +# Install Rosetta first +softwareupdate --install-rosetta + +# Install x86_64 Homebrew +arch -x86_64 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" + +# Install dependencies +arch -x86_64 /usr/local/bin/brew install mariadb-connector-c libpq +``` + +### Building on Apple Silicon for Intel + +To build Intel binaries on an Apple Silicon Mac, you need both Homebrew installations: +1. ARM64 Homebrew at `/opt/homebrew` (default) +2. x86_64 Homebrew at `/usr/local` (for cross-compilation) + +## Distribution Recommendations + +**For GitHub Releases:** +``` +✅ TablePro-v0.1.13-arm64.zip (~2MB zipped, ~6MB unzipped) +✅ TablePro-v0.1.13-x86_64.zip (~2MB zipped, ~6MB unzipped) +``` + +**For simple distribution:** +``` +✅ TablePro-v0.1.13-universal.zip (~4MB zipped, ~12MB unzipped) +``` + +Most modern apps (Discord, Slack, VSCode) distribute separate builds to save bandwidth. + +## Build Optimizations + +Release builds are optimized with: +- `DEPLOYMENT_POSTPROCESSING = YES` - Enables symbol stripping +- `COPY_PHASE_STRIP = YES` - Strips symbols during copy +- `DEAD_CODE_STRIPPING = YES` - Removes unused code + +These settings reduce binary size by ~60% (from 9.4MB to 3.7MB per architecture). + +## Quick Start + +```bash +# Development (current architecture) +xcodebuild -project TablePro.xcodeproj -scheme TablePro build + +# Release builds +./build-release.sh both +``` diff --git a/Libs/libmariadb.a b/Libs/libmariadb.a index 86adde50c..5a969b639 100644 Binary files a/Libs/libmariadb.a and b/Libs/libmariadb.a differ diff --git a/TablePro.xcodeproj/project.pbxproj b/TablePro.xcodeproj/project.pbxproj index 75a8a0eaa..aacca6499 100644 --- a/TablePro.xcodeproj/project.pbxproj +++ b/TablePro.xcodeproj/project.pbxproj @@ -239,8 +239,8 @@ LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; - ONLY_ACTIVE_ARCH = YES; SWIFT_COMPILATION_MODE = wholemodule; + /* NOTE: ONLY_ACTIVE_ARCH is intentionally not set to YES for Release to support multi-architecture builds. Building Release without the custom build script will attempt to build for all supported architectures by default. */ }; name = Release; }; @@ -267,7 +267,9 @@ GENERATE_INFOPLIST_FILE = YES; HEADER_SEARCH_PATHS = ( "/opt/homebrew/opt/mariadb-connector-c/include/mariadb", + "/usr/local/opt/mariadb-connector-c/include/mariadb", /opt/homebrew/opt/libpq/include, + /usr/local/opt/libpq/include, ); INFOPLIST_KEY_CFBundleDisplayName = TablePro; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools"; @@ -287,7 +289,7 @@ LIBRARY_SEARCH_PATHS = "$(PROJECT_DIR)/Libs"; MACOSX_DEPLOYMENT_TARGET = 14.6; MARKETING_VERSION = 0.1.13; - OTHER_LDFLAGS = ( + "OTHER_LDFLAGS[arch=arm64]" = ( "-force_load", "$(PROJECT_DIR)/Libs/libmariadb.a", "-L/opt/homebrew/opt/libpq/lib", @@ -297,6 +299,16 @@ "-lssl", "-lz", ); + "OTHER_LDFLAGS[arch=x86_64]" = ( + "-force_load", + "$(PROJECT_DIR)/Libs/libmariadb.a", + "-L/usr/local/opt/libpq/lib", + "-L/usr/local/opt/openssl/lib", + "-lpq", + "-lcrypto", + "-lssl", + "-lz", + ); PRODUCT_BUNDLE_IDENTIFIER = com.ngoquocdat.TablePro; PRODUCT_NAME = "$(TARGET_NAME)"; REGISTER_APP_GROUPS = YES; @@ -324,14 +336,16 @@ 5A1091D42EF17EDD0055EA7C /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - ARCHS = arm64; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES; AUTOMATION_APPLE_EVENTS = NO; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; + COPY_PHASE_STRIP = YES; CURRENT_PROJECT_VERSION = 13; + DEAD_CODE_STRIPPING = YES; + DEPLOYMENT_POSTPROCESSING = YES; DEVELOPMENT_TEAM = D7HJ5TFYCU; ENABLE_APP_SANDBOX = NO; ENABLE_HARDENED_RUNTIME = NO; @@ -345,7 +359,9 @@ GENERATE_INFOPLIST_FILE = YES; HEADER_SEARCH_PATHS = ( "/opt/homebrew/opt/mariadb-connector-c/include/mariadb", + "/usr/local/opt/mariadb-connector-c/include/mariadb", /opt/homebrew/opt/libpq/include, + /usr/local/opt/libpq/include, ); INFOPLIST_KEY_CFBundleDisplayName = TablePro; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools"; @@ -365,7 +381,7 @@ LIBRARY_SEARCH_PATHS = "$(PROJECT_DIR)/Libs"; MACOSX_DEPLOYMENT_TARGET = 14.6; MARKETING_VERSION = 0.1.13; - OTHER_LDFLAGS = ( + "OTHER_LDFLAGS[arch=arm64]" = ( "-force_load", "$(PROJECT_DIR)/Libs/libmariadb.a", "-L/opt/homebrew/opt/libpq/lib", @@ -375,6 +391,16 @@ "-lssl", "-lz", ); + "OTHER_LDFLAGS[arch=x86_64]" = ( + "-force_load", + "$(PROJECT_DIR)/Libs/libmariadb.a", + "-L/usr/local/opt/libpq/lib", + "-L/usr/local/opt/openssl/lib", + "-lpq", + "-lcrypto", + "-lssl", + "-lz", + ); PRODUCT_BUNDLE_IDENTIFIER = com.ngoquocdat.TablePro; PRODUCT_NAME = "$(TARGET_NAME)"; REGISTER_APP_GROUPS = YES; diff --git a/build-release.sh b/build-release.sh new file mode 100755 index 000000000..1c4bdbbf3 --- /dev/null +++ b/build-release.sh @@ -0,0 +1,194 @@ +#!/bin/bash +set -e + +# Build script for creating architecture-specific releases +# Usage: ./build-release.sh [arm64|x86_64|both] + +ARCH="${1:-both}" +PROJECT="TablePro.xcodeproj" +SCHEME="TablePro" +CONFIG="Release" +BUILD_DIR="build/Release" + +echo "🏗️ Building TablePro for: $ARCH" + +# Ensure libmariadb.a has correct architecture +prepare_mariadb() { + local target_arch=$1 + echo "📦 Preparing libmariadb.a for $target_arch..." + + # Change to Libs directory + cd Libs || { + echo "❌ FATAL: Cannot access Libs directory" + exit 1 + } + + # Check if universal library exists + if [ ! -f "libmariadb_universal.a" ]; then + echo "❌ ERROR: libmariadb_universal.a not found!" + echo "Run this first to create universal library:" + echo " lipo -create libmariadb_arm64.a libmariadb_x86_64.a -output libmariadb_universal.a" + cd - > /dev/null + exit 1 + fi + + # Extract thin slice for target architecture + if ! lipo libmariadb_universal.a -thin "$target_arch" -output libmariadb.a; then + echo "❌ FATAL: Failed to extract $target_arch slice from universal library" + echo "Ensure the universal library contains $target_arch architecture" + cd - > /dev/null + exit 1 + fi + + # Verify the output file was created + if [ ! -f "libmariadb.a" ]; then + echo "❌ FATAL: libmariadb.a was not created successfully" + cd - > /dev/null + exit 1 + fi + + # Get and display size + local size + size=$(ls -lh libmariadb.a 2>/dev/null | awk '{print $5}') + if [ -z "$size" ]; then + size="unknown" + fi + + echo "✅ libmariadb.a is now $target_arch-only ($size)" + + cd - > /dev/null || exit 1 +} + +build_for_arch() { + local arch=$1 + echo "" + echo "🔨 Building for $arch..." + + # Prepare architecture-specific mariadb library + prepare_mariadb "$arch" + + # Build with xcodebuild + echo "Running xcodebuild..." + if ! xcodebuild \ + -project "$PROJECT" \ + -scheme "$SCHEME" \ + -configuration "$CONFIG" \ + -arch "$arch" \ + ONLY_ACTIVE_ARCH=YES \ + clean build 2>&1 | tee "build-${arch}.log"; then + echo "❌ FATAL: xcodebuild failed for $arch" + echo "Check build-${arch}.log for details" + exit 1 + fi + echo "✅ Build succeeded for $arch" + + # Get binary path with validation + DERIVED_DATA=$(xcodebuild -project "$PROJECT" -scheme "$SCHEME" -showBuildSettings 2>&1 | grep -m 1 "BUILD_DIR" | awk '{print $3}') + + if [ -z "$DERIVED_DATA" ]; then + echo "❌ FATAL: Failed to determine build directory from xcodebuild settings" + echo "This usually indicates:" + echo " 1. The Xcode project is corrupted" + echo " 2. The scheme '$SCHEME' doesn't exist" + echo " 3. Xcode changed its output format" + echo "" + echo "Run this command to debug:" + echo " xcodebuild -project '$PROJECT' -scheme '$SCHEME' -showBuildSettings | grep BUILD_DIR" + exit 1 + fi + + APP_PATH="${DERIVED_DATA}/${CONFIG}/TablePro.app" + echo "📂 Expected app path: $APP_PATH" + + # Verify app bundle exists + if [ ! -d "$APP_PATH" ]; then + echo "❌ ERROR: Built app not found at expected path: $APP_PATH" + echo "Build may have failed silently" + exit 1 + fi + + # Create release directory + mkdir -p "$BUILD_DIR" || { + echo "❌ FATAL: Failed to create release directory: $BUILD_DIR" + exit 1 + } + + # Copy and rename app + OUTPUT_NAME="TablePro-${arch}.app" + echo "Copying app bundle to release directory..." + if ! cp -R "$APP_PATH" "$BUILD_DIR/$OUTPUT_NAME"; then + echo "❌ FATAL: Failed to copy app bundle" + echo "Source: $APP_PATH" + echo "Destination: $BUILD_DIR/$OUTPUT_NAME" + exit 1 + fi + + # Verify the copy succeeded + if [ ! -d "$BUILD_DIR/$OUTPUT_NAME" ]; then + echo "❌ FATAL: App bundle was not copied successfully" + exit 1 + fi + + # Verify binary exists inside the copied bundle + BINARY_PATH="$BUILD_DIR/$OUTPUT_NAME/Contents/MacOS/TablePro" + if [ ! -f "$BINARY_PATH" ]; then + echo "❌ FATAL: Binary not found in copied app bundle: $BINARY_PATH" + exit 1 + fi + + # Verify binary is not empty + if [ ! -s "$BINARY_PATH" ]; then + echo "❌ FATAL: Binary file is empty" + exit 1 + fi + + # Verify binary is executable + if [ ! -x "$BINARY_PATH" ]; then + echo "❌ FATAL: Binary is not executable" + exit 1 + fi + + # Get size + SIZE=$(ls -lh "$BINARY_PATH" 2>/dev/null | awk '{print $5}') + if [ -z "$SIZE" ]; then + echo "⚠️ WARNING: Could not determine binary size" + SIZE="unknown" + fi + + echo "✅ Built: $OUTPUT_NAME ($SIZE)" + + # Verify and display architecture + if ! lipo -info "$BINARY_PATH"; then + echo "⚠️ WARNING: Could not verify binary architecture" + fi +} + +# Main +case "$ARCH" in + arm64) + build_for_arch arm64 + ;; + x86_64) + build_for_arch x86_64 + ;; + both) + build_for_arch arm64 + echo "" + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo "" + build_for_arch x86_64 + ;; + *) + echo "Usage: $0 [arm64|x86_64|both]" + exit 1 + ;; +esac + +echo "" +echo "🎉 Build complete!" +echo "📁 Output: $BUILD_DIR/" + +if ! ls -lh "$BUILD_DIR" 2>/dev/null; then + echo "⚠️ WARNING: Could not list build directory contents" + echo "Directory may be empty or inaccessible" +fi