Fixes iOS build failures caused by linker picking up macOS SQLite libraries from pkgx instead of iOS system SQLite, resulting in undefined symbol errors for all sqlite3 functions. Changes: - Explicitly link system SQLite library (-lsqlite3) in podspec - Detect and unset pkgx environment variables during iOS builds - Add warnings to guide users if manual intervention needed The issue occurs when pkgx (or similar package managers) set DYLD_LIBRARY_PATH or PKGX_DIR, causing the linker to find macOS SQLite dylibs at /Users/*/.pkgx/sqlite.org/*/lib/libsqlite3.0.dylib instead of the iOS system SQLite library. This fix ensures the iOS build always uses the correct system SQLite library regardless of environment variable interference.
486 lines
16 KiB
Bash
Executable File
486 lines
16 KiB
Bash
Executable File
#!/bin/bash
|
||
|
||
# Exit on error
|
||
set -e
|
||
|
||
# Colors for output
|
||
RED='\033[0;31m'
|
||
GREEN='\033[0;32m'
|
||
YELLOW='\033[1;33m'
|
||
NC='\033[0m' # No Color
|
||
|
||
# Logging functions
|
||
log_info() {
|
||
echo -e "${GREEN}[INFO]${NC} $1"
|
||
}
|
||
|
||
log_warn() {
|
||
echo -e "${YELLOW}[WARN]${NC} $1"
|
||
}
|
||
|
||
log_error() {
|
||
echo -e "${RED}[ERROR]${NC} $1"
|
||
}
|
||
|
||
# Validation functions
|
||
check_command() {
|
||
if ! command -v $1 &> /dev/null; then
|
||
# Try rbenv shims for pod command
|
||
if [ "$1" = "pod" ] && [ -f "$HOME/.rbenv/shims/pod" ]; then
|
||
log_info "Found pod in rbenv shims"
|
||
return 0
|
||
fi
|
||
log_error "$1 is not installed. Please install it first."
|
||
exit 1
|
||
fi
|
||
}
|
||
|
||
# Get pod command (handles rbenv)
|
||
get_pod_command() {
|
||
if command -v pod &> /dev/null; then
|
||
echo "pod"
|
||
elif [ -f "$HOME/.rbenv/shims/pod" ]; then
|
||
echo "$HOME/.rbenv/shims/pod"
|
||
else
|
||
log_error "CocoaPods (pod) not found. Please install CocoaPods first."
|
||
exit 1
|
||
fi
|
||
}
|
||
|
||
check_environment() {
|
||
local PLATFORM=$1
|
||
|
||
# Check for required tools (always needed)
|
||
check_command "node"
|
||
check_command "npm"
|
||
|
||
# Check Node.js version
|
||
NODE_VERSION=$(node -v | cut -d. -f1 | tr -d 'v')
|
||
if [ "$NODE_VERSION" -lt 14 ]; then
|
||
log_error "Node.js version 14 or higher is required"
|
||
exit 1
|
||
fi
|
||
|
||
# Platform-specific checks
|
||
if [ "$PLATFORM" = "android" ] || [ "$PLATFORM" = "all" ]; then
|
||
check_command "java"
|
||
|
||
# Check for Gradle Wrapper instead of system gradle
|
||
if [ ! -f "android/gradlew" ]; then
|
||
log_error "Gradle wrapper not found at android/gradlew"
|
||
exit 1
|
||
fi
|
||
|
||
# Check Java version
|
||
JAVA_VERSION=$(java -version 2>&1 | head -n 1 | cut -d'"' -f2 | cut -d. -f1)
|
||
if [ "$JAVA_VERSION" -lt 11 ]; then
|
||
log_error "Java version 11 or higher is required"
|
||
exit 1
|
||
fi
|
||
|
||
# Check for Android SDK
|
||
if [ -z "$ANDROID_HOME" ]; then
|
||
log_error "ANDROID_HOME environment variable is not set"
|
||
exit 1
|
||
fi
|
||
fi
|
||
|
||
if [ "$PLATFORM" = "ios" ] || [ "$PLATFORM" = "all" ]; then
|
||
# iOS checks are done in build_ios() function
|
||
# to avoid failing if iOS tools aren't available when building Android only
|
||
:
|
||
fi
|
||
}
|
||
|
||
# Build functions
|
||
build_typescript() {
|
||
log_info "Building TypeScript..."
|
||
npm run clean
|
||
if ! npm run build; then
|
||
log_error "TypeScript build failed"
|
||
exit 1
|
||
fi
|
||
}
|
||
|
||
build_plugin_for_test_app() {
|
||
log_info "Building plugin for test app..."
|
||
|
||
# Ensure symlink is in place FIRST (before building)
|
||
# The test app expects the plugin at node_modules/@timesafari/daily-notification-plugin
|
||
SYMLINK="test-apps/daily-notification-test/node_modules/@timesafari/daily-notification-plugin"
|
||
if [ ! -L "$SYMLINK" ] || [ ! -e "$SYMLINK" ]; then
|
||
log_info "Creating symlink to plugin..."
|
||
mkdir -p "$(dirname "$SYMLINK")"
|
||
rm -f "$SYMLINK"
|
||
ln -sf "../../../" "$SYMLINK"
|
||
fi
|
||
|
||
# Build the plugin and test app from within the test app's Android project context
|
||
# This is necessary because Capacitor Android is only available as a project dependency
|
||
# The plugin will be built automatically as a dependency of the app
|
||
log_info "Building plugin and test app from test app context..."
|
||
cd test-apps/daily-notification-test/android || exit 1
|
||
|
||
# Build test app (this will automatically build the plugin as a dependency)
|
||
if ! ./gradlew clean assembleDebug; then
|
||
log_error "Build failed"
|
||
exit 1
|
||
fi
|
||
|
||
# Verify plugin was built (optional check)
|
||
PLUGIN_AAR="$(find . -name "*.aar" -path "*/timesafari-daily-notification-plugin/*" 2>/dev/null | head -1)"
|
||
if [ -n "$PLUGIN_AAR" ]; then
|
||
log_info "Plugin AAR built: $PLUGIN_AAR"
|
||
fi
|
||
|
||
# Verify APK was built
|
||
APK_FILE="app/build/outputs/apk/debug/app-debug.apk"
|
||
if [ ! -f "$APK_FILE" ]; then
|
||
log_error "APK file not found at $APK_FILE"
|
||
exit 1
|
||
fi
|
||
|
||
log_info "Build successful: $APK_FILE"
|
||
log_info "Install with: adb install -r $APK_FILE"
|
||
|
||
cd ../../..
|
||
}
|
||
|
||
build_plugin_for_test_app_ios() {
|
||
log_info "Building iOS test app..."
|
||
|
||
# Warn about pkgx interference with SQLite linking
|
||
if [ -n "$PKGX_DIR" ] || [ -d "$HOME/.pkgx" ]; then
|
||
log_warn "⚠️ pkgx detected - this may interfere with iOS SQLite linking"
|
||
log_warn " Temporarily unsetting PKGX_DIR and DYLD_LIBRARY_PATH for build..."
|
||
unset PKGX_DIR
|
||
unset DYLD_LIBRARY_PATH
|
||
unset LD_LIBRARY_PATH
|
||
fi
|
||
|
||
# Ensure symlink is in place FIRST (before building)
|
||
# The test app expects the plugin at node_modules/@timesafari/daily-notification-plugin
|
||
SYMLINK="test-apps/daily-notification-test/node_modules/@timesafari/daily-notification-plugin"
|
||
if [ ! -L "$SYMLINK" ] || [ ! -e "$SYMLINK" ]; then
|
||
log_info "Creating symlink to plugin..."
|
||
mkdir -p "$(dirname "$SYMLINK")"
|
||
rm -f "$SYMLINK"
|
||
ln -sf "../../../" "$SYMLINK"
|
||
fi
|
||
|
||
# Navigate to test app directory
|
||
cd test-apps/daily-notification-test || exit 1
|
||
|
||
# Build Vue app
|
||
log_info "Building Vue app..."
|
||
if ! npm run build; then
|
||
log_error "Vue app build failed"
|
||
exit 1
|
||
fi
|
||
|
||
# Sync Capacitor iOS (handles Podfile fixes and pod install)
|
||
log_info "Syncing Capacitor iOS..."
|
||
if ! npm run cap:sync:ios; then
|
||
log_error "Capacitor iOS sync failed"
|
||
exit 1
|
||
fi
|
||
|
||
# Build with xcodebuild
|
||
cd ios/App || exit 1
|
||
|
||
# Find workspace
|
||
if [ -d "App.xcworkspace" ]; then
|
||
WORKSPACE="App.xcworkspace"
|
||
SCHEME="App"
|
||
elif [ -d "App.xcodeproj" ]; then
|
||
PROJECT="App.xcodeproj"
|
||
SCHEME="App"
|
||
else
|
||
log_error "No Xcode workspace or project found in ios/App"
|
||
log_info "Expected: App.xcworkspace or App.xcodeproj"
|
||
exit 1
|
||
fi
|
||
|
||
# Auto-detect available iPhone simulator
|
||
log_info "Detecting available iPhone simulator..."
|
||
SIMULATOR_LINE=$(xcrun simctl list devices available 2>&1 | grep -i "iPhone" | head -1)
|
||
|
||
if [ -n "$SIMULATOR_LINE" ]; then
|
||
# Extract device ID (UUID in parentheses)
|
||
SIMULATOR_ID=$(echo "$SIMULATOR_LINE" | sed -E 's/.*\(([A-F0-9-]+)\).*/\1/')
|
||
# Extract device name (everything before the first parenthesis)
|
||
SIMULATOR_NAME=$(echo "$SIMULATOR_LINE" | sed -E 's/^[[:space:]]*([^(]+).*/\1/' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
|
||
|
||
if [ -n "$SIMULATOR_ID" ] && [ "$SIMULATOR_ID" != "Shutdown" ] && [ "$SIMULATOR_ID" != "Booted" ]; then
|
||
DESTINATION="platform=iOS Simulator,id=$SIMULATOR_ID"
|
||
log_info "Building for iOS Simulator ($SIMULATOR_NAME, ID: $SIMULATOR_ID)..."
|
||
elif [ -n "$SIMULATOR_NAME" ]; then
|
||
DESTINATION="platform=iOS Simulator,name=$SIMULATOR_NAME"
|
||
log_info "Building for iOS Simulator ($SIMULATOR_NAME)..."
|
||
else
|
||
DESTINATION="platform=iOS Simulator,name=iPhone 14"
|
||
log_warn "Using default simulator destination: iPhone 14"
|
||
fi
|
||
else
|
||
DESTINATION="platform=iOS Simulator,name=iPhone 14"
|
||
log_warn "No iPhone simulators found, using default: iPhone 14"
|
||
fi
|
||
|
||
# Build for simulator
|
||
log_info "Building iOS app for simulator..."
|
||
if [ -n "$WORKSPACE" ]; then
|
||
if ! xcodebuild build \
|
||
-workspace "$WORKSPACE" \
|
||
-scheme "$SCHEME" \
|
||
-configuration Debug \
|
||
-sdk iphonesimulator \
|
||
-destination "$DESTINATION" \
|
||
CODE_SIGN_IDENTITY="" \
|
||
CODE_SIGNING_REQUIRED=NO \
|
||
CODE_SIGNING_ALLOWED=NO; then
|
||
log_error "iOS build failed"
|
||
exit 1
|
||
fi
|
||
else
|
||
if ! xcodebuild build \
|
||
-project "$PROJECT" \
|
||
-scheme "$SCHEME" \
|
||
-configuration Debug \
|
||
-sdk iphonesimulator \
|
||
-destination "$DESTINATION" \
|
||
CODE_SIGN_IDENTITY="" \
|
||
CODE_SIGNING_REQUIRED=NO \
|
||
CODE_SIGNING_ALLOWED=NO; then
|
||
log_error "iOS build failed"
|
||
exit 1
|
||
fi
|
||
fi
|
||
|
||
log_info "iOS build successful!"
|
||
log_info "App built in DerivedData. To run: xcrun simctl boot <simulator-id> && xcrun simctl install booted <app-path>"
|
||
|
||
cd ../../..
|
||
}
|
||
|
||
build_android() {
|
||
log_info "Building Android..."
|
||
|
||
# Detect project type
|
||
if [ -d "test-apps/daily-notification-test" ]; then
|
||
log_info "Detected test app. Building plugin and test app together..."
|
||
build_plugin_for_test_app
|
||
return 0
|
||
fi
|
||
|
||
cd android || exit 1
|
||
|
||
# Check if this is a plugin development project
|
||
if [ ! -f "capacitor-cordova-android-plugins/cordova.variables.gradle" ]; then
|
||
log_warn "This appears to be a plugin development project"
|
||
log_warn "Android test app not properly initialized"
|
||
|
||
# =============================================================================
|
||
# AUTOMATIC FIX: capacitor.build.gradle for Plugin Development Projects
|
||
# =============================================================================
|
||
#
|
||
# PROBLEM: The capacitor.build.gradle file is auto-generated by Capacitor CLI
|
||
# and includes a line that tries to load a file that doesn't exist in plugin
|
||
# development projects:
|
||
# apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle"
|
||
#
|
||
# WHY THIS HAPPENS:
|
||
# - This file is generated by 'npx cap sync', 'npx cap update', etc.
|
||
# - It assumes a full Capacitor app with proper plugin integration
|
||
# - Plugin development projects don't have the full Capacitor setup
|
||
#
|
||
# THE FIX:
|
||
# - Comment out the problematic line to prevent build failures
|
||
# - Add explanatory comment about why it's commented out
|
||
# - This fix gets applied automatically every time the build script runs
|
||
#
|
||
# WHEN THIS FIX GETS OVERWRITTEN:
|
||
# - Running 'npx cap sync' will regenerate the file and remove our fix
|
||
# - Running 'npx cap update' will regenerate the file and remove our fix
|
||
# - Running 'npx cap add android' will regenerate the file and remove our fix
|
||
#
|
||
# HOW TO RESTORE THE FIX:
|
||
# - Run this build script again (it will reapply the fix automatically)
|
||
# - Or run: ./scripts/fix-capacitor-build.sh
|
||
# - Or manually comment out the problematic line
|
||
#
|
||
# =============================================================================
|
||
|
||
if [ -f "app/capacitor.build.gradle" ]; then
|
||
if grep -q "^apply from: \"../capacitor-cordova-android-plugins/cordova.variables.gradle\"" "app/capacitor.build.gradle"; then
|
||
log_info "🔧 Applying automatic fix to capacitor.build.gradle..."
|
||
log_info " Reason: Plugin development project missing Capacitor integration files"
|
||
log_info " Fix: Commenting out problematic 'apply from' line"
|
||
|
||
# Apply the fix by commenting out the problematic line
|
||
# Handle macOS vs Linux sed differences
|
||
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||
# macOS sed requires empty string after -i
|
||
sed -i '' 's/^apply from: "\.\.\/capacitor-cordova-android-plugins\/cordova\.variables\.gradle"/\/\/ Plugin development project - no Capacitor integration files needed\n\/\/ &/' "app/capacitor.build.gradle"
|
||
else
|
||
# Linux sed
|
||
sed -i 's/^apply from: "\.\.\/capacitor-cordova-android-plugins\/cordova\.variables\.gradle"/\/\/ Plugin development project - no Capacitor integration files needed\n\/\/ &/' "app/capacitor.build.gradle"
|
||
fi
|
||
|
||
log_info "✅ Fix applied successfully"
|
||
log_warn "⚠️ Note: This fix will be lost if you run 'npx cap sync' or 'npx cap update'"
|
||
log_info "💡 To restore the fix later, run this build script again or use: ./scripts/fix-capacitor-build.sh"
|
||
else
|
||
log_info "ℹ️ capacitor.build.gradle already has the fix applied"
|
||
fi
|
||
else
|
||
log_warn "⚠️ capacitor.build.gradle not found - this may indicate a different project structure"
|
||
fi
|
||
|
||
log_warn "Plugin source code has been built successfully"
|
||
log_warn "To test the plugin, use it in a Capacitor app instead"
|
||
cd ..
|
||
return 0
|
||
fi
|
||
|
||
# Check for gradle wrapper
|
||
if [ ! -f "./gradlew" ]; then
|
||
log_error "Gradle wrapper not found"
|
||
exit 1
|
||
fi
|
||
|
||
# Clean and build
|
||
if ! ./gradlew clean; then
|
||
log_error "Android clean failed"
|
||
exit 1
|
||
fi
|
||
|
||
if ! ./gradlew assembleRelease; then
|
||
log_error "Android build failed"
|
||
exit 1
|
||
fi
|
||
|
||
# Verify AAR file exists
|
||
AAR_FILE="capacitor-cordova-android-plugins/build/outputs/aar/capacitor-cordova-android-plugins-release.aar"
|
||
if [ ! -f "$AAR_FILE" ]; then
|
||
log_error "AAR file not found at $AAR_FILE"
|
||
exit 1
|
||
fi
|
||
|
||
log_info "Android build successful: $AAR_FILE"
|
||
cd ..
|
||
}
|
||
|
||
build_ios() {
|
||
log_info "Building iOS..."
|
||
|
||
# Check iOS-specific requirements
|
||
check_command "xcodebuild"
|
||
check_command "pod"
|
||
|
||
# Check for Xcode
|
||
if ! xcodebuild -version &> /dev/null; then
|
||
log_error "Xcode is not installed or not properly configured"
|
||
exit 1
|
||
fi
|
||
|
||
# Warn about pkgx interference with SQLite linking
|
||
if [ -n "$PKGX_DIR" ] || [ -d "$HOME/.pkgx" ]; then
|
||
log_warn "⚠️ pkgx detected - this may interfere with iOS SQLite linking"
|
||
log_warn " If you see 'sqlite3' undefined symbol errors, try:"
|
||
log_warn " unset PKGX_DIR && unset DYLD_LIBRARY_PATH && ./scripts/build-native.sh --platform ios"
|
||
fi
|
||
|
||
# Detect project type
|
||
if [ -d "test-apps/daily-notification-test" ]; then
|
||
log_info "Detected test app. Building plugin and test app together..."
|
||
build_plugin_for_test_app_ios
|
||
return 0
|
||
fi
|
||
|
||
# For plugin-only builds, navigate to iOS directory
|
||
cd ios || exit 1
|
||
|
||
log_warn "This appears to be a plugin development project"
|
||
log_warn "iOS plugin source code has been built successfully"
|
||
log_warn "To test the plugin, use it in a Capacitor app instead"
|
||
|
||
# Install CocoaPods dependencies if Podfile exists
|
||
if [ -f "Podfile" ]; then
|
||
log_info "Installing CocoaPods dependencies..."
|
||
POD_CMD=$(get_pod_command)
|
||
if ! $POD_CMD install; then
|
||
log_error "CocoaPods installation failed"
|
||
exit 1
|
||
fi
|
||
fi
|
||
|
||
# Build plugin framework (if workspace exists)
|
||
if [ -d "DailyNotificationPlugin.xcworkspace" ]; then
|
||
log_info "Building iOS plugin framework..."
|
||
if ! xcodebuild build \
|
||
-workspace DailyNotificationPlugin.xcworkspace \
|
||
-scheme DailyNotificationPlugin \
|
||
-configuration Release \
|
||
-sdk iphoneos \
|
||
CODE_SIGN_IDENTITY="" \
|
||
CODE_SIGNING_REQUIRED=NO \
|
||
CODE_SIGNING_ALLOWED=NO; then
|
||
log_error "iOS plugin build failed"
|
||
exit 1
|
||
fi
|
||
log_info "iOS plugin build successful"
|
||
else
|
||
log_warn "No Xcode workspace found - plugin may need to be built in a host app"
|
||
fi
|
||
|
||
cd ..
|
||
}
|
||
|
||
# Main build process
|
||
main() {
|
||
log_info "Starting build process..."
|
||
|
||
# Parse command line arguments
|
||
BUILD_PLATFORM="all"
|
||
while [[ $# -gt 0 ]]; do
|
||
case $1 in
|
||
--platform)
|
||
BUILD_PLATFORM="$2"
|
||
shift 2
|
||
;;
|
||
*)
|
||
log_error "Unknown option: $1"
|
||
exit 1
|
||
;;
|
||
esac
|
||
done
|
||
|
||
# Build TypeScript (needed for all platforms)
|
||
build_typescript
|
||
|
||
# Check environment (after parsing platform so we can check conditionally)
|
||
check_environment "$BUILD_PLATFORM"
|
||
|
||
# Build based on platform
|
||
case $BUILD_PLATFORM in
|
||
"android")
|
||
build_android
|
||
;;
|
||
"ios")
|
||
build_ios
|
||
;;
|
||
"all")
|
||
build_android
|
||
build_ios
|
||
;;
|
||
*)
|
||
log_error "Invalid platform: $BUILD_PLATFORM. Use 'android', 'ios', or 'all'"
|
||
exit 1
|
||
;;
|
||
esac
|
||
|
||
log_info "Build completed successfully!"
|
||
}
|
||
|
||
# Run main function with all arguments
|
||
main "$@" |