Files
daily-notification-plugin/scripts/build-native.sh
Jose Olarte III 630fd3de81 fix(ios): resolve SQLite linking conflicts with pkgx
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.
2026-01-14 18:41:46 +08:00

486 lines
16 KiB
Bash
Executable File
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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 "$@"