Files
daily-notification-plugin/scripts/build-native.sh
Jose Olarte III 2915fe7438 fix(build): add SQLite conflict detection and Command Line Tools verification
Prevents iOS build failures caused by pkgx SQLite linking conflicts and
ensures Xcode Command Line Tools are properly installed.

Problem:
- pkgx installs SQLite built for macOS, causing linker errors when building
  for iOS simulator: "linking in dylib built for 'macOS'"
- Missing Command Line Tools cause build failures without clear error messages

Changes:
- Add check_sqlite_conflicts() function
  - Detects pkgx SQLite installations in ~/.pkgx
  - Warns about macOS dylibs that will cause iOS simulator build failures
  - Checks for system SQLite from Command Line Tools
  - Validates library paths (DYLD_LIBRARY_PATH, LD_LIBRARY_PATH)

- Add check_command_line_tools() function
  - Verifies Xcode Command Line Tools are installed and configured
  - Checks for xcodebuild availability
  - Verifies sqlite3 is available (part of Command Line Tools)
  - Provides clear error messages with installation instructions

- Enhance pkgx detection in iOS build functions
  - Specifically searches for pkgx SQLite dylibs
  - Automatically removes pkgx paths from PATH environment variable
  - Provides detailed warnings about detected conflicts
  - Cleans all problematic environment variables before building

- Integrate checks into environment validation
  - Runs automatically when building for iOS
  - Provides early warnings before build starts
  - Fails fast with clear error messages if tools are missing

This fixes the linker error:
  "ld: building for 'iOS-simulator', but linking in dylib
   (/Users/trent/.pkgx/sqlite.org/v3.44.2/lib/libsqlite3.0.dylib)
   built for 'macOS'"

The build script now:
- Detects pkgx SQLite conflicts before building
- Automatically fixes environment variables
- Verifies Command Line Tools are installed
- Provides clear guidance for manual fixes if needed

Files modified:
- scripts/build-native.sh
2026-01-15 18:34:33 +08:00

624 lines
22 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_sqlite_conflicts() {
local PLATFORM=$1
if [ "$PLATFORM" != "ios" ] && [ "$PLATFORM" != "all" ]; then
return 0
fi
# Check for pkgx SQLite that might interfere with iOS builds
if [ -d "$HOME/.pkgx" ]; then
# Look for pkgx SQLite installations
PKGX_SQLITE=$(find "$HOME/.pkgx" -name "libsqlite3*.dylib" 2>/dev/null | head -1)
if [ -n "$PKGX_SQLITE" ]; then
log_warn "⚠️ pkgx SQLite detected: $PKGX_SQLITE"
log_warn " This may cause iOS build failures (linking macOS dylib for iOS simulator)"
log_warn " The build script will unset problematic environment variables"
# Check if library paths point to pkgx
if [ -n "$DYLD_LIBRARY_PATH" ] && echo "$DYLD_LIBRARY_PATH" | grep -q "\.pkgx"; then
log_warn " DYLD_LIBRARY_PATH contains pkgx paths - will be unset for build"
fi
if [ -n "$LD_LIBRARY_PATH" ] && echo "$LD_LIBRARY_PATH" | grep -q "\.pkgx"; then
log_warn " LD_LIBRARY_PATH contains pkgx paths - will be unset for build"
fi
fi
fi
# Check for system SQLite (from Command Line Tools)
if command -v sqlite3 &> /dev/null; then
SQLITE_PATH=$(which sqlite3)
if [ "$SQLITE_PATH" = "/usr/bin/sqlite3" ]; then
log_info "✅ System SQLite found (from Command Line Tools): $SQLITE_PATH"
else
log_warn "⚠️ SQLite found at non-standard location: $SQLITE_PATH"
log_warn " Ensure this is compatible with iOS builds"
fi
else
log_warn "⚠️ sqlite3 command not found - Command Line Tools may not be installed"
log_warn " Install with: xcode-select --install"
fi
}
check_command_line_tools() {
if [ "$(uname)" != "Darwin" ]; then
return 0
fi
# Check if xcode-select points to a valid developer directory
if ! xcode-select -p &> /dev/null; then
log_error "Xcode Command Line Tools not configured"
log_error " Run: xcode-select --install"
exit 1
fi
# Check if xcodebuild is available
if ! command -v xcodebuild &> /dev/null; then
log_error "xcodebuild not found - Command Line Tools may be incomplete"
log_error " Run: xcode-select --install"
exit 1
fi
# Verify sqlite3 is available (part of Command Line Tools)
if ! command -v sqlite3 &> /dev/null; then
log_warn "⚠️ sqlite3 not found - Command Line Tools may be incomplete"
log_warn " This may cause iOS build issues"
log_warn " Run: xcode-select --install"
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
# Check Command Line Tools
check_command_line_tools
# Check for SQLite conflicts
check_sqlite_conflicts "$PLATFORM"
# iOS-specific 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
# Navigate to test app directory
cd test-apps/daily-notification-test || exit 1
# Sync Capacitor Android (creates necessary project structure)
log_info "Syncing Capacitor Android..."
if ! npm run cap:sync:android; then
log_error "Capacitor Android sync failed"
exit 1
fi
# Navigate to android directory
cd android || exit 1
# Fix capacitor.build.gradle if it references missing cordova.variables.gradle
# This file is auto-generated by Capacitor and may reference files that don't exist
if [ -f "app/capacitor.build.gradle" ]; then
if grep -q "^apply from: \"../capacitor-cordova-android-plugins/cordova.variables.gradle\"" "app/capacitor.build.gradle"; then
# Check if the referenced file actually exists
if [ ! -f "capacitor-cordova-android-plugins/cordova.variables.gradle" ]; then
log_info "🔧 Applying fix to capacitor.build.gradle..."
log_info " Reason: Referenced cordova.variables.gradle doesn't exist"
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
# Use a simpler approach: just comment out the line
sed -i '' 's|^apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle"|// &|' "app/capacitor.build.gradle"
else
# Linux sed
sed -i 's|^apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle"|// &|' "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'"
fi
fi
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..."
# 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..."
# Fix pkgx interference with SQLite linking
# pkgx installs SQLite built for macOS, which causes linker errors when building for iOS simulator
if [ -n "$PKGX_DIR" ] || [ -d "$HOME/.pkgx" ]; then
log_warn "⚠️ pkgx detected - fixing SQLite linking conflicts..."
# Check for pkgx SQLite specifically
PKGX_SQLITE=$(find "$HOME/.pkgx" -name "libsqlite3*.dylib" 2>/dev/null | head -1)
if [ -n "$PKGX_SQLITE" ]; then
log_warn " Found pkgx SQLite: $PKGX_SQLITE"
log_warn " This is built for macOS and will cause iOS simulator build failures"
fi
log_warn " Temporarily unsetting PKGX_DIR and library paths for build..."
unset PKGX_DIR
unset DYLD_LIBRARY_PATH
unset LD_LIBRARY_PATH
# Also unset any pkgx-related paths that might be in PATH
export PATH=$(echo "$PATH" | tr ':' '\n' | grep -v "\.pkgx" | tr '\n' ':' | sed 's/:$//')
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
# Install dependencies if node_modules doesn't exist or is missing key packages
if [ ! -d "node_modules" ] || [ ! -f "node_modules/.bin/run-p" ]; then
log_info "Installing test app dependencies..."
if ! npm install; then
log_error "Failed to install test app dependencies"
exit 1
fi
fi
# 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
# Check for SQLite conflicts (already done in check_environment, but remind here)
if [ -d "$HOME/.pkgx" ]; then
PKGX_SQLITE=$(find "$HOME/.pkgx" -name "libsqlite3*.dylib" 2>/dev/null | head -1)
if [ -n "$PKGX_SQLITE" ]; then
log_warn "⚠️ pkgx SQLite detected - build script will handle this automatically"
log_warn " If you still see linker errors, manually run:"
log_warn " unset PKGX_DIR DYLD_LIBRARY_PATH LD_LIBRARY_PATH && ./scripts/build-native.sh --platform ios"
fi
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 "$@"