3131 # Function to validate a single framework variant
3232 validate_framework() {
3333 local variant_path="$1"
34- local variant_name=$(basename $(dirname "$variant_path"))
34+ local variant_name=$(basename "$(dirname "$variant_path")")
35+ local variant_name_lower=$(echo "$variant_name" | tr '[:upper:]' '[:lower:]')
36+ local requires_versioned=false
37+
38+ # Mac Catalyst and macOS variants must be packaged as versioned bundles
39+ if [[ "$variant_name_lower" == *"maccatalyst"* || "$variant_name_lower" == *"macos"* ]]; then
40+ requires_versioned=true
41+ fi
3542
3643 if [ ! -d "$variant_path" ]; then
3744 echo "⚠️ Skipping $variant_name (not found)"
4047
4148 echo "📦 Validating $variant_name variant..."
4249
43- # Check if framework uses versioning (macOS arm64 ) or flat structure (iOS)
50+ # Check if framework uses versioning (macOS / Catalyst ) or flat structure (iOS)
4451 if [ -d "$variant_path/Versions" ]; then
4552 echo " ✓ Versioned framework detected"
4653
5259 echo " ✓ Versions/Current symlink exists"
5360
5461 # Check Versions/Current points to A
55- local current_target=$(readlink "$variant_path/Versions/Current")
62+ local current_target
63+ current_target=$(readlink "$variant_path/Versions/Current")
5664 if [ "$current_target" != "A" ]; then
5765 echo " ❌ ERROR: Versions/Current points to '$current_target' instead of 'A'"
5866 return 1
@@ -67,14 +75,33 @@ jobs:
6775 return 1
6876 fi
6977
70- local target=$(readlink "$variant_path/$symlink")
78+ local target
79+ target=$(readlink "$variant_path/$symlink")
7180 if [[ ! "$target" =~ ^Versions/Current/ ]]; then
7281 echo " ❌ ERROR: $symlink points to '$target' instead of Versions/Current/*"
7382 return 1
7483 fi
7584 done
7685 echo " ✓ Top-level symlinks point to Versions/Current/*"
7786
87+ # Ensure Info.plist is stored within the versioned resources
88+ local info_plist_versioned="$variant_path/Versions/A/Resources/Info.plist"
89+ local info_plist_current="$variant_path/Versions/Current/Resources/Info.plist"
90+ local info_plist_resources="$variant_path/Resources/Info.plist"
91+ if [ ! -f "$info_plist_versioned" ]; then
92+ echo " ❌ ERROR: Info.plist missing at Versions/A/Resources/Info.plist"
93+ return 1
94+ fi
95+ if [ ! -f "$info_plist_current" ]; then
96+ echo " ❌ ERROR: Info.plist missing at Versions/Current/Resources/Info.plist"
97+ return 1
98+ fi
99+ if [ ! -f "$info_plist_resources" ]; then
100+ echo " ❌ ERROR: Info.plist missing via Resources/Info.plist symlink"
101+ return 1
102+ fi
103+ echo " ✓ Info.plist correctly nested inside Resources"
104+
78105 # Check that the actual binary exists and is valid
79106 local binary_path="$variant_path/Versions/A/ESpeakNG"
80107 if [ ! -f "$binary_path" ]; then
84111 echo " ✓ Binary file exists: Versions/A/ESpeakNG"
85112
86113 # Verify it's a valid Mach-O binary
87- local file_type=$(file "$binary_path" | grep -o "Mach-O\|executable")
114+ local file_type
115+ file_type=$(file "$binary_path" | grep -o "Mach-O\|executable")
88116 if [ -z "$file_type" ]; then
89117 echo " ❌ ERROR: Binary is not a valid Mach-O file"
90118 return 1
@@ -98,6 +126,11 @@ jobs:
98126 echo " ⚠️ Code signature not valid (expected for local builds)"
99127 fi
100128 else
129+ if [ "$requires_versioned" = true ]; then
130+ echo " ❌ ERROR: $variant_name must use a versioned framework layout (missing Versions/ directory)"
131+ return 1
132+ fi
133+
101134 # Flat framework structure (iOS)
102135 echo " ✓ Flat framework structure detected (iOS)"
103136
@@ -110,7 +143,8 @@ jobs:
110143 echo " ✓ Binary file exists: ESpeakNG"
111144
112145 # Verify it's a valid Mach-O binary
113- local file_type=$(file "$binary_path" | grep -o "Mach-O\|executable")
146+ local file_type
147+ file_type=$(file "$binary_path" | grep -o "Mach-O\|executable")
114148 if [ -z "$file_type" ]; then
115149 echo " ❌ ERROR: Binary is not a valid Mach-O file"
116150 return 1
@@ -121,7 +155,6 @@ jobs:
121155 echo ""
122156 return 0
123157 }
124-
125158 # Validate all framework variants
126159 all_valid=true
127160 for variant in "$FRAMEWORK_PATH"/*; do
@@ -147,32 +180,37 @@ jobs:
147180 echo "🔎 Checking for common framework structure issues..."
148181 echo ""
149182
150- # Check for case sensitivity issues in binary names
151- for framework in "$FRAMEWORK_PATH"/**/ESpeakNG.framework/Versions/A; do
152- if [ -d "$framework" ]; then
153- # Use ls with grep to check for exact case typo on case-insensitive filesystems
154- if ls "$framework" 2>/dev/null | grep -x "ESPeakNG" > /dev/null; then
155- echo "❌ ERROR: Found 'ESPeakNG' (typo) instead of 'ESpeakNG'"
156- echo " This would cause dyld errors at runtime"
157- exit 1
158- fi
183+ echo "🔠 Verifying binary casing inside versioned frameworks..."
184+ found_versioned=false
185+ while IFS= read -r -d '' version_dir; do
186+ found_versioned=true
187+ actual_name=$(python3 -c 'import os, sys; path=sys.argv[1]; target="espeakng"; print(next((n for n in os.listdir(path) if n.lower()==target), ""))' "$version_dir")
188+ if [ -z "$actual_name" ]; then
189+ echo "❌ ERROR: No ESpeakNG binary found inside $version_dir"
190+ exit 1
159191 fi
160- done
192+ if [ "$actual_name" != "ESpeakNG" ]; then
193+ echo "❌ ERROR: Binary '$actual_name' found in $version_dir (expected ESpeakNG)"
194+ echo " This would cause dyld errors on case-sensitive filesystems"
195+ exit 1
196+ fi
197+ done < <(find "$FRAMEWORK_PATH" -type d -path "*/ESpeakNG.framework/Versions/A" -print0)
161198
162- echo "✅ No case sensitivity issues found"
163- echo ""
199+ if [ "$found_versioned" = false ]; then
200+ echo " ⚠️ No versioned frameworks found to inspect"
201+ else
202+ echo " ✓ Binary casing validated in versioned slices"
203+ fi
164204
165- # Check for broken symlinks
205+ echo ""
166206 echo "🔗 Checking for broken symlinks..."
167- for framework in "$FRAMEWORK_PATH"/**/ESpeakNG.framework; do
168- if [ -d "$framework" ]; then
169- while IFS= read -r -d '' link; do
170- if [ -L "$link" ] && ! [ -e "$link" ]; then
171- echo "❌ ERROR: Broken symlink: $(basename $link)"
172- exit 1
173- fi
174- done < <(find "$framework" -type l -print0)
175- fi
176- done
207+ while IFS= read -r -d '' framework; do
208+ while IFS= read -r -d '' link; do
209+ if [ -L "$link" ] && ! [ -e "$link" ]; then
210+ echo "❌ ERROR: Broken symlink: $(basename "$link")"
211+ exit 1
212+ fi
213+ done < <(find "$framework" -type l -print0)
214+ done < <(find "$FRAMEWORK_PATH" -type d -name "ESpeakNG.framework" -print0)
177215
178216 echo "✅ All symlinks are valid"
0 commit comments