This commit is contained in:
2026-02-04 02:21:23 +08:00
commit 208786aa90
112 changed files with 9571 additions and 0 deletions

116
build/ios/Assets.xcassets Normal file
View File

@@ -0,0 +1,116 @@
{
"info" : {
"author" : "xcode",
"version" : 1
},
"images" : [
{
"filename" : "icon-20@2x.png",
"idiom" : "iphone",
"scale" : "2x",
"size" : "20x20"
},
{
"filename" : "icon-20@3x.png",
"idiom" : "iphone",
"scale" : "3x",
"size" : "20x20"
},
{
"filename" : "icon-29@2x.png",
"idiom" : "iphone",
"scale" : "2x",
"size" : "29x29"
},
{
"filename" : "icon-29@3x.png",
"idiom" : "iphone",
"scale" : "3x",
"size" : "29x29"
},
{
"filename" : "icon-40@2x.png",
"idiom" : "iphone",
"scale" : "2x",
"size" : "40x40"
},
{
"filename" : "icon-40@3x.png",
"idiom" : "iphone",
"scale" : "3x",
"size" : "40x40"
},
{
"filename" : "icon-60@2x.png",
"idiom" : "iphone",
"scale" : "2x",
"size" : "60x60"
},
{
"filename" : "icon-60@3x.png",
"idiom" : "iphone",
"scale" : "3x",
"size" : "60x60"
},
{
"filename" : "icon-20.png",
"idiom" : "ipad",
"scale" : "1x",
"size" : "20x20"
},
{
"filename" : "icon-20@2x.png",
"idiom" : "ipad",
"scale" : "2x",
"size" : "20x20"
},
{
"filename" : "icon-29.png",
"idiom" : "ipad",
"scale" : "1x",
"size" : "29x29"
},
{
"filename" : "icon-29@2x.png",
"idiom" : "ipad",
"scale" : "2x",
"size" : "29x29"
},
{
"filename" : "icon-40.png",
"idiom" : "ipad",
"scale" : "1x",
"size" : "40x40"
},
{
"filename" : "icon-40@2x.png",
"idiom" : "ipad",
"scale" : "2x",
"size" : "40x40"
},
{
"filename" : "icon-76.png",
"idiom" : "ipad",
"scale" : "1x",
"size" : "76x76"
},
{
"filename" : "icon-76@2x.png",
"idiom" : "ipad",
"scale" : "2x",
"size" : "76x76"
},
{
"filename" : "icon-83.5@2x.png",
"idiom" : "ipad",
"scale" : "2x",
"size" : "83.5x83.5"
},
{
"filename" : "icon-1024.png",
"idiom" : "ios-marketing",
"scale" : "1x",
"size" : "1024x1024"
}
]
}

62
build/ios/Info.dev.plist Normal file
View File

@@ -0,0 +1,62 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleExecutable</key>
<string>mesh-drop</string>
<key>CFBundleIdentifier</key>
<string>com.example.meshdrop.dev</string>
<key>CFBundleName</key>
<string>My Product (Dev)</string>
<key>CFBundleDisplayName</key>
<string>My Product (Dev)</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>0.1.0-dev</string>
<key>CFBundleVersion</key>
<string>0.1.0</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>MinimumOSVersion</key>
<string>15.0</string>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>
<string>arm64</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
<key>NSAllowsLocalNetworking</key>
<true/>
</dict>
<!-- Development mode enabled -->
<key>WailsDevelopmentMode</key>
<true/>
<key>NSHumanReadableCopyright</key>
<string>© 2026, My Company</string>
<key>CFBundleGetInfoString</key>
<string>This is a comment</string>
</dict>
</plist>

59
build/ios/Info.plist Normal file
View File

@@ -0,0 +1,59 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleExecutable</key>
<string>mesh-drop</string>
<key>CFBundleIdentifier</key>
<string>com.example.meshdrop</string>
<key>CFBundleName</key>
<string>My Product</string>
<key>CFBundleDisplayName</key>
<string>My Product</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>0.1.0</string>
<key>CFBundleVersion</key>
<string>0.1.0</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>MinimumOSVersion</key>
<string>15.0</string>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>
<string>arm64</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<false/>
<key>NSAllowsLocalNetworking</key>
<true/>
</dict>
<key>NSHumanReadableCopyright</key>
<string>© 2026, My Company</string>
<key>CFBundleGetInfoString</key>
<string>This is a comment</string>
</dict>
</plist>

View File

@@ -0,0 +1,53 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="21701" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<device id="retina6_12" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21678"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="EHf-IW-A2E">
<objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<rect key="frame" x="0.0" y="0.0" width="393" height="852"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="My Product" textAlignment="center" lineBreakMode="middleTruncation" baselineAdjustment="alignBaselines" minimumFontSize="18" translatesAutoresizingMaskIntoConstraints="NO" id="GJd-Yh-RWb">
<rect key="frame" x="0.0" y="397" width="393" height="43"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="36"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="A mesh-drop application" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="9" translatesAutoresizingMaskIntoConstraints="NO" id="MN2-I3-ftu">
<rect key="frame" x="0.0" y="448" width="393" height="21"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<viewLayoutGuide key="safeArea" id="Bcu-3y-fUS"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="Bcu-3y-fUS" firstAttribute="centerX" secondItem="GJd-Yh-RWb" secondAttribute="centerX" id="Q3B-4B-g5h"/>
<constraint firstItem="GJd-Yh-RWb" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="bottom" multiplier="1/2" constant="-20" id="moa-c2-u7t"/>
<constraint firstItem="GJd-Yh-RWb" firstAttribute="leading" secondItem="Bcu-3y-fUS" secondAttribute="leading" symbolic="YES" id="x7j-FC-K8j"/>
<constraint firstItem="MN2-I3-ftu" firstAttribute="top" secondItem="GJd-Yh-RWb" secondAttribute="bottom" constant="8" symbolic="YES" id="cPy-rs-vsC"/>
<constraint firstItem="MN2-I3-ftu" firstAttribute="centerX" secondItem="Bcu-3y-fUS" secondAttribute="centerX" id="OQL-iM-xY6"/>
<constraint firstItem="MN2-I3-ftu" firstAttribute="leading" secondItem="Bcu-3y-fUS" secondAttribute="leading" symbolic="YES" id="Dti-5h-tvW"/>
</constraints>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="53" y="375"/>
</scene>
</scenes>
</document>

293
build/ios/Taskfile.yml Normal file
View File

@@ -0,0 +1,293 @@
version: '3'
includes:
common: ../Taskfile.yml
vars:
BUNDLE_ID: '{{.BUNDLE_ID | default "com.wails.app"}}'
# SDK_PATH is computed lazily at task-level to avoid errors on non-macOS systems
# Each task that needs it defines SDK_PATH in its own vars section
tasks:
install:deps:
summary: Check and install iOS development dependencies
cmds:
- go run build/ios/scripts/deps/install_deps.go
env:
TASK_FORCE_YES: '{{if .YES}}true{{else}}false{{end}}'
prompt: This will check and install iOS development dependencies. Continue?
# Note: Bindings generation may show CGO warnings for iOS C imports.
# These warnings are harmless and don't affect the generated bindings,
# as the generator only needs to parse Go types, not C implementations.
build:
summary: Creates a build of the application for iOS
deps:
- task: generate:ios:overlay
- task: generate:ios:xcode
- task: common:go:mod:tidy
- task: generate:ios:bindings
vars:
BUILD_FLAGS:
ref: .BUILD_FLAGS
- task: common:build:frontend
vars:
BUILD_FLAGS:
ref: .BUILD_FLAGS
PRODUCTION:
ref: .PRODUCTION
- task: common:generate:icons
cmds:
- echo "Building iOS app {{.APP_NAME}}..."
- go build -buildmode=c-archive -overlay build/ios/xcode/overlay.json {{.BUILD_FLAGS}} -o {{.OUTPUT}}.a
vars:
BUILD_FLAGS: '{{if eq .PRODUCTION "true"}}-tags production,ios -trimpath -buildvcs=false -ldflags="-w -s"{{else}}-tags ios,debug -buildvcs=false -gcflags=all="-l"{{end}}'
DEFAULT_OUTPUT: '{{.BIN_DIR}}/{{.APP_NAME}}'
OUTPUT: '{{ .OUTPUT | default .DEFAULT_OUTPUT }}'
SDK_PATH:
sh: xcrun --sdk iphonesimulator --show-sdk-path
env:
GOOS: ios
CGO_ENABLED: 1
GOARCH: '{{.ARCH | default "arm64"}}'
PRODUCTION: '{{.PRODUCTION | default "false"}}'
CGO_CFLAGS: '-isysroot {{.SDK_PATH}} -target arm64-apple-ios15.0-simulator -mios-simulator-version-min=15.0'
CGO_LDFLAGS: '-isysroot {{.SDK_PATH}} -target arm64-apple-ios15.0-simulator'
compile:objc:
summary: Compile Objective-C iOS wrapper
vars:
SDK_PATH:
sh: xcrun --sdk iphonesimulator --show-sdk-path
cmds:
- xcrun -sdk iphonesimulator clang -target arm64-apple-ios15.0-simulator -isysroot {{.SDK_PATH}} -framework Foundation -framework UIKit -framework WebKit -o {{.BIN_DIR}}/{{.APP_NAME}} build/ios/main.m
- codesign --force --sign - "{{.BIN_DIR}}/{{.APP_NAME}}"
package:
summary: Packages a production build of the application into a `.app` bundle
deps:
- task: build
vars:
PRODUCTION: "true"
cmds:
- task: create:app:bundle
create:app:bundle:
summary: Creates an iOS `.app` bundle
cmds:
- rm -rf "{{.BIN_DIR}}/{{.APP_NAME}}.app"
- mkdir -p "{{.BIN_DIR}}/{{.APP_NAME}}.app"
- cp "{{.BIN_DIR}}/{{.APP_NAME}}" "{{.BIN_DIR}}/{{.APP_NAME}}.app/"
- cp build/ios/Info.plist "{{.BIN_DIR}}/{{.APP_NAME}}.app/"
- |
# Compile asset catalog and embed icons in the app bundle
APP_BUNDLE="{{.BIN_DIR}}/{{.APP_NAME}}.app"
AC_IN="build/ios/xcode/main/Assets.xcassets"
if [ -d "$AC_IN" ]; then
TMP_AC=$(mktemp -d)
xcrun actool \
--compile "$TMP_AC" \
--app-icon AppIcon \
--platform iphonesimulator \
--minimum-deployment-target 15.0 \
--product-type com.apple.product-type.application \
--target-device iphone \
--target-device ipad \
--output-partial-info-plist "$APP_BUNDLE/assetcatalog_generated_info.plist" \
"$AC_IN"
if [ -f "$TMP_AC/Assets.car" ]; then
cp -f "$TMP_AC/Assets.car" "$APP_BUNDLE/Assets.car"
fi
rm -rf "$TMP_AC"
if [ -f "$APP_BUNDLE/assetcatalog_generated_info.plist" ]; then
/usr/libexec/PlistBuddy -c "Merge $APP_BUNDLE/assetcatalog_generated_info.plist" "$APP_BUNDLE/Info.plist" || true
fi
fi
- codesign --force --sign - "{{.BIN_DIR}}/{{.APP_NAME}}.app"
deploy-simulator:
summary: Deploy to iOS Simulator
deps: [package]
cmds:
- xcrun simctl terminate booted {{.BUNDLE_ID}} 2>/dev/null || true
- xcrun simctl uninstall booted {{.BUNDLE_ID}} 2>/dev/null || true
- xcrun simctl install booted "{{.BIN_DIR}}/{{.APP_NAME}}.app"
- xcrun simctl launch booted {{.BUNDLE_ID}}
compile:ios:
summary: Compile the iOS executable from Go archive and main.m
deps:
- task: build
vars:
SDK_PATH:
sh: xcrun --sdk iphonesimulator --show-sdk-path
cmds:
- |
MAIN_M=build/ios/xcode/main/main.m
if [ ! -f "$MAIN_M" ]; then
MAIN_M=build/ios/main.m
fi
xcrun -sdk iphonesimulator clang \
-target arm64-apple-ios15.0-simulator \
-isysroot {{.SDK_PATH}} \
-framework Foundation -framework UIKit -framework WebKit \
-framework Security -framework CoreFoundation \
-lresolv \
-o "{{.BIN_DIR}}/{{.APP_NAME | lower}}" \
"$MAIN_M" "{{.BIN_DIR}}/{{.APP_NAME}}.a"
generate:ios:bindings:
internal: true
summary: Generates bindings for iOS with proper CGO flags
sources:
- "**/*.go"
- go.mod
- go.sum
generates:
- frontend/bindings/**/*
vars:
SDK_PATH:
sh: xcrun --sdk iphonesimulator --show-sdk-path
cmds:
- wails3 generate bindings -f '{{.BUILD_FLAGS}}' -clean=true
env:
GOOS: ios
CGO_ENABLED: 1
GOARCH: '{{.ARCH | default "arm64"}}'
CGO_CFLAGS: '-isysroot {{.SDK_PATH}} -target arm64-apple-ios15.0-simulator -mios-simulator-version-min=15.0'
CGO_LDFLAGS: '-isysroot {{.SDK_PATH}} -target arm64-apple-ios15.0-simulator'
ensure-simulator:
internal: true
summary: Ensure iOS Simulator is running and booted
silent: true
cmds:
- |
if ! xcrun simctl list devices booted | grep -q "Booted"; then
echo "Starting iOS Simulator..."
# Get first available iPhone device
DEVICE_ID=$(xcrun simctl list devices available | grep "iPhone" | head -1 | grep -o "[A-F0-9-]\{36\}" || true)
if [ -z "$DEVICE_ID" ]; then
echo "No iPhone simulator found. Creating one..."
RUNTIME=$(xcrun simctl list runtimes | grep iOS | tail -1 | awk '{print $NF}')
DEVICE_ID=$(xcrun simctl create "iPhone 15 Pro" "iPhone 15 Pro" "$RUNTIME")
fi
# Boot the device
echo "Booting device $DEVICE_ID..."
xcrun simctl boot "$DEVICE_ID" 2>/dev/null || true
# Open Simulator app
open -a Simulator
# Wait for boot (max 30 seconds)
for i in {1..30}; do
if xcrun simctl list devices booted | grep -q "Booted"; then
echo "Simulator booted successfully"
break
fi
sleep 1
done
# Final check
if ! xcrun simctl list devices booted | grep -q "Booted"; then
echo "Failed to boot simulator after 30 seconds"
exit 1
fi
fi
preconditions:
- sh: command -v xcrun
msg: "xcrun not found. Please run 'wails3 task ios:install:deps' to install iOS development dependencies"
generate:ios:overlay:
internal: true
summary: Generate Go build overlay and iOS shim
sources:
- build/config.yml
generates:
- build/ios/xcode/overlay.json
- build/ios/xcode/gen/main_ios.gen.go
cmds:
- wails3 ios overlay:gen -out build/ios/xcode/overlay.json -config build/config.yml
generate:ios:xcode:
internal: true
summary: Generate iOS Xcode project structure and assets
sources:
- build/config.yml
- build/appicon.png
generates:
- build/ios/xcode/main/main.m
- build/ios/xcode/main/Assets.xcassets/**/*
- build/ios/xcode/project.pbxproj
cmds:
- wails3 ios xcode:gen -outdir build/ios/xcode -config build/config.yml
run:
summary: Run the application in iOS Simulator
deps:
- task: ensure-simulator
- task: compile:ios
cmds:
- rm -rf "{{.BIN_DIR}}/{{.APP_NAME}}.dev.app"
- mkdir -p "{{.BIN_DIR}}/{{.APP_NAME}}.dev.app"
- cp "{{.BIN_DIR}}/{{.APP_NAME | lower}}" "{{.BIN_DIR}}/{{.APP_NAME}}.dev.app/{{.APP_NAME | lower}}"
- cp build/ios/Info.dev.plist "{{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Info.plist"
- |
# Compile asset catalog and embed icons for dev bundle
APP_BUNDLE="{{.BIN_DIR}}/{{.APP_NAME}}.dev.app"
AC_IN="build/ios/xcode/main/Assets.xcassets"
if [ -d "$AC_IN" ]; then
TMP_AC=$(mktemp -d)
xcrun actool \
--compile "$TMP_AC" \
--app-icon AppIcon \
--platform iphonesimulator \
--minimum-deployment-target 15.0 \
--product-type com.apple.product-type.application \
--target-device iphone \
--target-device ipad \
--output-partial-info-plist "$APP_BUNDLE/assetcatalog_generated_info.plist" \
"$AC_IN"
if [ -f "$TMP_AC/Assets.car" ]; then
cp -f "$TMP_AC/Assets.car" "$APP_BUNDLE/Assets.car"
fi
rm -rf "$TMP_AC"
if [ -f "$APP_BUNDLE/assetcatalog_generated_info.plist" ]; then
/usr/libexec/PlistBuddy -c "Merge $APP_BUNDLE/assetcatalog_generated_info.plist" "$APP_BUNDLE/Info.plist" || true
fi
fi
- codesign --force --sign - "{{.BIN_DIR}}/{{.APP_NAME}}.dev.app"
- xcrun simctl terminate booted "com.wails.{{.APP_NAME | lower}}.dev" 2>/dev/null || true
- xcrun simctl uninstall booted "com.wails.{{.APP_NAME | lower}}.dev" 2>/dev/null || true
- xcrun simctl install booted "{{.BIN_DIR}}/{{.APP_NAME}}.dev.app"
- xcrun simctl launch booted "com.wails.{{.APP_NAME | lower}}.dev"
xcode:
summary: Open the generated Xcode project for this app
cmds:
- task: generate:ios:xcode
- open build/ios/xcode/main.xcodeproj
logs:
summary: Stream iOS Simulator logs filtered to this app
cmds:
- |
xcrun simctl spawn booted log stream \
--level debug \
--style compact \
--predicate 'senderImagePath CONTAINS[c] "{{.APP_NAME | lower}}.app/" OR composedMessage CONTAINS[c] "{{.APP_NAME | lower}}" OR eventMessage CONTAINS[c] "{{.APP_NAME | lower}}" OR process == "{{.APP_NAME | lower}}" OR category CONTAINS[c] "{{.APP_NAME | lower}}"'
logs:dev:
summary: Stream logs for the dev bundle (used by `task ios:run`)
cmds:
- |
xcrun simctl spawn booted log stream \
--level debug \
--style compact \
--predicate 'senderImagePath CONTAINS[c] ".dev.app/" OR subsystem == "com.wails.{{.APP_NAME | lower}}.dev" OR process == "{{.APP_NAME | lower}}"'
logs:wide:
summary: Wide log stream to help discover the exact process/bundle identifiers
cmds:
- |
xcrun simctl spawn booted log stream \
--level debug \
--style compact \
--predicate 'senderImagePath CONTAINS[c] ".app/"'

View File

@@ -0,0 +1,10 @@
//go:build !ios
package main
import "github.com/wailsapp/wails/v3/pkg/application"
// modifyOptionsForIOS is a no-op on non-iOS platforms
func modifyOptionsForIOS(opts *application.Options) {
// No modifications needed for non-iOS platforms
}

View File

@@ -0,0 +1,11 @@
//go:build ios
package main
import "github.com/wailsapp/wails/v3/pkg/application"
// modifyOptionsForIOS adjusts the application options for iOS
func modifyOptionsForIOS(opts *application.Options) {
// Disable signal handlers on iOS to prevent crashes
opts.DisableDefaultSignalHandler = true
}

72
build/ios/build.sh Normal file
View File

@@ -0,0 +1,72 @@
#!/bin/bash
set -e
# Build configuration
APP_NAME="mesh-drop"
BUNDLE_ID="com.example.meshdrop"
VERSION="0.1.0"
BUILD_NUMBER="0.1.0"
BUILD_DIR="build/ios"
TARGET="simulator"
echo "Building iOS app: $APP_NAME"
echo "Bundle ID: $BUNDLE_ID"
echo "Version: $VERSION ($BUILD_NUMBER)"
echo "Target: $TARGET"
# Ensure build directory exists
mkdir -p "$BUILD_DIR"
# Determine SDK and target architecture
if [ "$TARGET" = "simulator" ]; then
SDK="iphonesimulator"
ARCH="arm64-apple-ios15.0-simulator"
elif [ "$TARGET" = "device" ]; then
SDK="iphoneos"
ARCH="arm64-apple-ios15.0"
else
echo "Unknown target: $TARGET"
exit 1
fi
# Get SDK path
SDK_PATH=$(xcrun --sdk $SDK --show-sdk-path)
# Compile the application
echo "Compiling with SDK: $SDK"
xcrun -sdk $SDK clang \
-target $ARCH \
-isysroot "$SDK_PATH" \
-framework Foundation \
-framework UIKit \
-framework WebKit \
-framework CoreGraphics \
-o "$BUILD_DIR/$APP_NAME" \
"$BUILD_DIR/main.m"
# Create app bundle
echo "Creating app bundle..."
APP_BUNDLE="$BUILD_DIR/$APP_NAME.app"
rm -rf "$APP_BUNDLE"
mkdir -p "$APP_BUNDLE"
# Move executable
mv "$BUILD_DIR/$APP_NAME" "$APP_BUNDLE/"
# Copy Info.plist
cp "$BUILD_DIR/Info.plist" "$APP_BUNDLE/"
# Sign the app
echo "Signing app..."
codesign --force --sign - "$APP_BUNDLE"
echo "Build complete: $APP_BUNDLE"
# Deploy to simulator if requested
if [ "$TARGET" = "simulator" ]; then
echo "Deploying to simulator..."
xcrun simctl terminate booted "$BUNDLE_ID" 2>/dev/null || true
xcrun simctl install booted "$APP_BUNDLE"
xcrun simctl launch booted "$BUNDLE_ID"
echo "App launched on simulator"
fi

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<!-- Development entitlements -->
<key>get-task-allow</key>
<true/>
<!-- App Sandbox -->
<key>com.apple.security.app-sandbox</key>
<true/>
<!-- Network access -->
<key>com.apple.security.network.client</key>
<true/>
<!-- File access (read-only) -->
<key>com.apple.security.files.user-selected.read-only</key>
<true/>
</dict>
</plist>

3
build/ios/icon.png Normal file
View File

@@ -0,0 +1,3 @@
# iOS Icon Placeholder
# This file should be replaced with the actual app icon (1024x1024 PNG)
# The build process will generate all required icon sizes from this base icon

23
build/ios/main.m Normal file
View File

@@ -0,0 +1,23 @@
//go:build ios
// Minimal bootstrap: delegate comes from Go archive (WailsAppDelegate)
#import <UIKit/UIKit.h>
#include <stdio.h>
// External Go initialization function from the c-archive (declare before use)
extern void WailsIOSMain();
int main(int argc, char * argv[]) {
@autoreleasepool {
// Disable buffering so stdout/stderr from Go log.Printf flush immediately
setvbuf(stdout, NULL, _IONBF, 0);
setvbuf(stderr, NULL, _IONBF, 0);
// Start Go runtime on a background queue to avoid blocking main thread/UI
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{
WailsIOSMain();
});
// Run UIApplicationMain using WailsAppDelegate provided by the Go archive
return UIApplicationMain(argc, argv, nil, @"WailsAppDelegate");
}
}

24
build/ios/main_ios.go Normal file
View File

@@ -0,0 +1,24 @@
//go:build ios
package main
import (
"C"
)
// For iOS builds, we need to export a function that can be called from Objective-C
// This wrapper allows us to keep the original main.go unmodified
//export WailsIOSMain
func WailsIOSMain() {
// DO NOT lock the goroutine to the current OS thread on iOS!
// This causes signal handling issues:
// "signal 16 received on thread with no signal stack"
// "fatal error: non-Go code disabled sigaltstack"
// iOS apps run in a sandboxed environment where the Go runtime's
// signal handling doesn't work the same way as desktop platforms.
// Call the actual main function from main.go
// This ensures all the user's code is executed
main()
}

222
build/ios/project.pbxproj Normal file
View File

@@ -0,0 +1,222 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {};
objectVersion = 56;
objects = {
/* Begin PBXBuildFile section */
C0DEBEEF0000000000000001 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = C0DEBEEF0000000000000002 /* main.m */; };
C0DEBEEF00000000000000F1 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C0DEBEEF0000000000000101 /* UIKit.framework */; };
C0DEBEEF00000000000000F2 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C0DEBEEF0000000000000102 /* Foundation.framework */; };
C0DEBEEF00000000000000F3 /* WebKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C0DEBEEF0000000000000103 /* WebKit.framework */; };
C0DEBEEF00000000000000F4 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C0DEBEEF0000000000000104 /* Security.framework */; };
C0DEBEEF00000000000000F5 /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C0DEBEEF0000000000000105 /* CoreFoundation.framework */; };
C0DEBEEF00000000000000F6 /* libresolv.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = C0DEBEEF0000000000000106 /* libresolv.tbd */; };
C0DEBEEF00000000000000F7 /* My Product.a in Frameworks */ = {isa = PBXBuildFile; fileRef = C0DEBEEF0000000000000107 /* My Product.a */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
C0DEBEEF0000000000000002 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
C0DEBEEF0000000000000003 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
C0DEBEEF0000000000000004 /* My Product.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "My Product.app"; sourceTree = BUILT_PRODUCTS_DIR; };
C0DEBEEF0000000000000101 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; };
C0DEBEEF0000000000000102 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
C0DEBEEF0000000000000103 /* WebKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebKit.framework; path = System/Library/Frameworks/WebKit.framework; sourceTree = SDKROOT; };
C0DEBEEF0000000000000104 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; };
C0DEBEEF0000000000000105 /* CoreFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreFoundation.framework; path = System/Library/Frameworks/CoreFoundation.framework; sourceTree = SDKROOT; };
C0DEBEEF0000000000000106 /* libresolv.tbd */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.text-based-dylib-definition; name = libresolv.tbd; path = usr/lib/libresolv.tbd; sourceTree = SDKROOT; };
C0DEBEEF0000000000000107 /* My Product.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = "My Product.a"; path = ../../../bin/My Product.a; sourceTree = SOURCE_ROOT; };
/* End PBXFileReference section */
/* Begin PBXGroup section */
C0DEBEEF0000000000000010 = {
isa = PBXGroup;
children = (
C0DEBEEF0000000000000020 /* Products */,
C0DEBEEF0000000000000045 /* Frameworks */,
C0DEBEEF0000000000000030 /* main */,
);
sourceTree = "<group>";
};
C0DEBEEF0000000000000020 /* Products */ = {
isa = PBXGroup;
children = (
C0DEBEEF0000000000000004 /* My Product.app */,
);
name = Products;
sourceTree = "<group>";
};
C0DEBEEF0000000000000030 /* main */ = {
isa = PBXGroup;
children = (
C0DEBEEF0000000000000002 /* main.m */,
C0DEBEEF0000000000000003 /* Info.plist */,
);
path = main;
sourceTree = SOURCE_ROOT;
};
C0DEBEEF0000000000000045 /* Frameworks */ = {
isa = PBXGroup;
children = (
C0DEBEEF0000000000000101 /* UIKit.framework */,
C0DEBEEF0000000000000102 /* Foundation.framework */,
C0DEBEEF0000000000000103 /* WebKit.framework */,
C0DEBEEF0000000000000104 /* Security.framework */,
C0DEBEEF0000000000000105 /* CoreFoundation.framework */,
C0DEBEEF0000000000000106 /* libresolv.tbd */,
C0DEBEEF0000000000000107 /* My Product.a */,
);
name = Frameworks;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
C0DEBEEF0000000000000040 /* My Product */ = {
isa = PBXNativeTarget;
buildConfigurationList = C0DEBEEF0000000000000070 /* Build configuration list for PBXNativeTarget "My Product" */;
buildPhases = (
C0DEBEEF0000000000000055 /* Prebuild: Wails Go Archive */,
C0DEBEEF0000000000000050 /* Sources */,
C0DEBEEF0000000000000056 /* Frameworks */,
);
buildRules = (
);
dependencies = (
);
name = "My Product";
productName = "My Product";
productReference = C0DEBEEF0000000000000004 /* My Product.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
C0DEBEEF0000000000000060 /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 1500;
ORGANIZATIONNAME = "My Company";
TargetAttributes = {
C0DEBEEF0000000000000040 = {
CreatedOnToolsVersion = 15.0;
};
};
};
buildConfigurationList = C0DEBEEF0000000000000080 /* Build configuration list for PBXProject "main" */;
compatibilityVersion = "Xcode 15.0";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
);
mainGroup = C0DEBEEF0000000000000010;
productRefGroup = C0DEBEEF0000000000000020 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
C0DEBEEF0000000000000040 /* My Product */,
);
};
/* End PBXProject section */
/* Begin PBXFrameworksBuildPhase section */
C0DEBEEF0000000000000056 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
C0DEBEEF00000000000000F7 /* My Product.a in Frameworks */,
C0DEBEEF00000000000000F1 /* UIKit.framework in Frameworks */,
C0DEBEEF00000000000000F2 /* Foundation.framework in Frameworks */,
C0DEBEEF00000000000000F3 /* WebKit.framework in Frameworks */,
C0DEBEEF00000000000000F4 /* Security.framework in Frameworks */,
C0DEBEEF00000000000000F5 /* CoreFoundation.framework in Frameworks */,
C0DEBEEF00000000000000F6 /* libresolv.tbd in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
C0DEBEEF0000000000000055 /* Prebuild: Wails Go Archive */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
);
name = "Prebuild: Wails Go Archive";
outputFileListPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "set -e\nAPP_ROOT=\"${PROJECT_DIR}/../../..\"\nSDK_PATH=$(xcrun --sdk iphonesimulator --show-sdk-path)\nexport GOOS=ios\nexport GOARCH=arm64\nexport CGO_ENABLED=1\nexport CGO_CFLAGS=\"-isysroot ${SDK_PATH} -target arm64-apple-ios15.0-simulator -mios-simulator-version-min=15.0\"\nexport CGO_LDFLAGS=\"-isysroot ${SDK_PATH} -target arm64-apple-ios15.0-simulator\"\ncd \"${APP_ROOT}\"\n# Ensure overlay exists\nif [ ! -f build/ios/xcode/overlay.json ]; then\n wails3 ios overlay:gen -out build/ios/xcode/overlay.json -config build/config.yml || true\nfi\n# Build Go c-archive if missing or older than sources\nif [ ! -f bin/My Product.a ]; then\n echo \"Building Go c-archive...\"\n go build -buildmode=c-archive -overlay build/ios/xcode/overlay.json -o bin/My Product.a\nfi\n";
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
C0DEBEEF0000000000000050 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
C0DEBEEF0000000000000001 /* main.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin XCBuildConfiguration section */
C0DEBEEF0000000000000090 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
INFOPLIST_FILE = main/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
PRODUCT_BUNDLE_IDENTIFIER = "com.example.meshdrop";
PRODUCT_NAME = "My Product";
CODE_SIGNING_ALLOWED = NO;
SDKROOT = iphonesimulator;
};
name = Debug;
};
C0DEBEEF00000000000000A0 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
INFOPLIST_FILE = main/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
PRODUCT_BUNDLE_IDENTIFIER = "com.example.meshdrop";
PRODUCT_NAME = "My Product";
CODE_SIGNING_ALLOWED = NO;
SDKROOT = iphonesimulator;
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
C0DEBEEF0000000000000070 /* Build configuration list for PBXNativeTarget "My Product" */ = {
isa = XCConfigurationList;
buildConfigurations = (
C0DEBEEF0000000000000090 /* Debug */,
C0DEBEEF00000000000000A0 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Debug;
};
C0DEBEEF0000000000000080 /* Build configuration list for PBXProject "main" */ = {
isa = XCConfigurationList;
buildConfigurations = (
C0DEBEEF0000000000000090 /* Debug */,
C0DEBEEF00000000000000A0 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Debug;
};
/* End XCConfigurationList section */
};
rootObject = C0DEBEEF0000000000000060 /* Project object */;
}

View File

@@ -0,0 +1,319 @@
// install_deps.go - iOS development dependency checker
// This script checks for required iOS development tools.
// It's designed to be portable across different shells by using Go instead of shell scripts.
//
// Usage:
// go run install_deps.go # Interactive mode
// TASK_FORCE_YES=true go run install_deps.go # Auto-accept prompts
// CI=true go run install_deps.go # CI mode (auto-accept)
package main
import (
"bufio"
"fmt"
"os"
"os/exec"
"strings"
)
type Dependency struct {
Name string
CheckFunc func() (bool, string) // Returns (success, details)
Required bool
InstallCmd []string
InstallMsg string
SuccessMsg string
FailureMsg string
}
func main() {
fmt.Println("Checking iOS development dependencies...")
fmt.Println("=" + strings.Repeat("=", 50))
fmt.Println()
hasErrors := false
dependencies := []Dependency{
{
Name: "Xcode",
CheckFunc: func() (bool, string) {
// Check if xcodebuild exists
if !checkCommand([]string{"xcodebuild", "-version"}) {
return false, ""
}
// Get version info
out, err := exec.Command("xcodebuild", "-version").Output()
if err != nil {
return false, ""
}
lines := strings.Split(string(out), "\n")
if len(lines) > 0 {
return true, strings.TrimSpace(lines[0])
}
return true, ""
},
Required: true,
InstallMsg: "Please install Xcode from the Mac App Store:\n https://apps.apple.com/app/xcode/id497799835\n Xcode is REQUIRED for iOS development (includes iOS SDKs, simulators, and frameworks)",
SuccessMsg: "✅ Xcode found",
FailureMsg: "❌ Xcode not found (REQUIRED)",
},
{
Name: "Xcode Developer Path",
CheckFunc: func() (bool, string) {
// Check if xcode-select points to a valid Xcode path
out, err := exec.Command("xcode-select", "-p").Output()
if err != nil {
return false, "xcode-select not configured"
}
path := strings.TrimSpace(string(out))
// Check if path exists and is in Xcode.app
if _, err := os.Stat(path); err != nil {
return false, "Invalid Xcode path"
}
// Verify it's pointing to Xcode.app (not just Command Line Tools)
if !strings.Contains(path, "Xcode.app") {
return false, fmt.Sprintf("Points to %s (should be Xcode.app)", path)
}
return true, path
},
Required: true,
InstallCmd: []string{"sudo", "xcode-select", "-s", "/Applications/Xcode.app/Contents/Developer"},
InstallMsg: "Xcode developer path needs to be configured",
SuccessMsg: "✅ Xcode developer path configured",
FailureMsg: "❌ Xcode developer path not configured correctly",
},
{
Name: "iOS SDK",
CheckFunc: func() (bool, string) {
// Get the iOS Simulator SDK path
cmd := exec.Command("xcrun", "--sdk", "iphonesimulator", "--show-sdk-path")
output, err := cmd.Output()
if err != nil {
return false, "Cannot find iOS SDK"
}
sdkPath := strings.TrimSpace(string(output))
// Check if the SDK path exists
if _, err := os.Stat(sdkPath); err != nil {
return false, "iOS SDK path not found"
}
// Check for UIKit framework (essential for iOS development)
uikitPath := fmt.Sprintf("%s/System/Library/Frameworks/UIKit.framework", sdkPath)
if _, err := os.Stat(uikitPath); err != nil {
return false, "UIKit.framework not found"
}
// Get SDK version
versionCmd := exec.Command("xcrun", "--sdk", "iphonesimulator", "--show-sdk-version")
versionOut, _ := versionCmd.Output()
version := strings.TrimSpace(string(versionOut))
return true, fmt.Sprintf("iOS %s SDK", version)
},
Required: true,
InstallMsg: "iOS SDK comes with Xcode. Please ensure Xcode is properly installed.",
SuccessMsg: "✅ iOS SDK found with UIKit framework",
FailureMsg: "❌ iOS SDK not found or incomplete",
},
{
Name: "iOS Simulator Runtime",
CheckFunc: func() (bool, string) {
if !checkCommand([]string{"xcrun", "simctl", "help"}) {
return false, ""
}
// Check if we can list runtimes
out, err := exec.Command("xcrun", "simctl", "list", "runtimes").Output()
if err != nil {
return false, "Cannot access simulator"
}
// Count iOS runtimes
lines := strings.Split(string(out), "\n")
count := 0
var versions []string
for _, line := range lines {
if strings.Contains(line, "iOS") && !strings.Contains(line, "unavailable") {
count++
// Extract version number
if parts := strings.Fields(line); len(parts) > 2 {
for _, part := range parts {
if strings.HasPrefix(part, "(") && strings.HasSuffix(part, ")") {
versions = append(versions, strings.Trim(part, "()"))
break
}
}
}
}
}
if count > 0 {
return true, fmt.Sprintf("%d runtime(s): %s", count, strings.Join(versions, ", "))
}
return false, "No iOS runtimes installed"
},
Required: true,
InstallMsg: "iOS Simulator runtimes come with Xcode. You may need to download them:\n Xcode → Settings → Platforms → iOS",
SuccessMsg: "✅ iOS Simulator runtime available",
FailureMsg: "❌ iOS Simulator runtime not available",
},
}
// Check each dependency
for _, dep := range dependencies {
success, details := dep.CheckFunc()
if success {
msg := dep.SuccessMsg
if details != "" {
msg = fmt.Sprintf("%s (%s)", dep.SuccessMsg, details)
}
fmt.Println(msg)
} else {
fmt.Println(dep.FailureMsg)
if details != "" {
fmt.Printf(" Details: %s\n", details)
}
if dep.Required {
hasErrors = true
if len(dep.InstallCmd) > 0 {
fmt.Println()
fmt.Println(" " + dep.InstallMsg)
fmt.Printf(" Fix command: %s\n", strings.Join(dep.InstallCmd, " "))
if promptUser("Do you want to run this command?") {
fmt.Println("Running command...")
cmd := exec.Command(dep.InstallCmd[0], dep.InstallCmd[1:]...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Stdin = os.Stdin
if err := cmd.Run(); err != nil {
fmt.Printf("Command failed: %v\n", err)
os.Exit(1)
}
fmt.Println("✅ Command completed. Please run this check again.")
} else {
fmt.Printf(" Please run manually: %s\n", strings.Join(dep.InstallCmd, " "))
}
} else {
fmt.Println(" " + dep.InstallMsg)
}
}
}
}
// Check for iPhone simulators
fmt.Println()
fmt.Println("Checking for iPhone simulator devices...")
if !checkCommand([]string{"xcrun", "simctl", "list", "devices"}) {
fmt.Println("❌ Cannot check for iPhone simulators")
hasErrors = true
} else {
out, err := exec.Command("xcrun", "simctl", "list", "devices").Output()
if err != nil {
fmt.Println("❌ Failed to list simulator devices")
hasErrors = true
} else if !strings.Contains(string(out), "iPhone") {
fmt.Println("⚠️ No iPhone simulator devices found")
fmt.Println()
// Get the latest iOS runtime
runtimeOut, err := exec.Command("xcrun", "simctl", "list", "runtimes").Output()
if err != nil {
fmt.Println(" Failed to get iOS runtimes:", err)
} else {
lines := strings.Split(string(runtimeOut), "\n")
var latestRuntime string
for _, line := range lines {
if strings.Contains(line, "iOS") && !strings.Contains(line, "unavailable") {
// Extract runtime identifier
parts := strings.Fields(line)
if len(parts) > 0 {
latestRuntime = parts[len(parts)-1]
}
}
}
if latestRuntime == "" {
fmt.Println(" No iOS runtime found. Please install iOS simulators in Xcode:")
fmt.Println(" Xcode → Settings → Platforms → iOS")
} else {
fmt.Println(" Would you like to create an iPhone 15 Pro simulator?")
createCmd := []string{"xcrun", "simctl", "create", "iPhone 15 Pro", "iPhone 15 Pro", latestRuntime}
fmt.Printf(" Command: %s\n", strings.Join(createCmd, " "))
if promptUser("Create simulator?") {
cmd := exec.Command(createCmd[0], createCmd[1:]...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
fmt.Printf(" Failed to create simulator: %v\n", err)
} else {
fmt.Println(" ✅ iPhone 15 Pro simulator created")
}
} else {
fmt.Println(" Skipping simulator creation")
fmt.Printf(" Create manually: %s\n", strings.Join(createCmd, " "))
}
}
}
} else {
// Count iPhone devices
count := 0
lines := strings.Split(string(out), "\n")
for _, line := range lines {
if strings.Contains(line, "iPhone") && !strings.Contains(line, "unavailable") {
count++
}
}
fmt.Printf("✅ %d iPhone simulator device(s) available\n", count)
}
}
// Final summary
fmt.Println()
fmt.Println("=" + strings.Repeat("=", 50))
if hasErrors {
fmt.Println("❌ Some required dependencies are missing or misconfigured.")
fmt.Println()
fmt.Println("Quick setup guide:")
fmt.Println("1. Install Xcode from Mac App Store (if not installed)")
fmt.Println("2. Open Xcode once and agree to the license")
fmt.Println("3. Install additional components when prompted")
fmt.Println("4. Run: sudo xcode-select -s /Applications/Xcode.app/Contents/Developer")
fmt.Println("5. Download iOS simulators: Xcode → Settings → Platforms → iOS")
fmt.Println("6. Run this check again")
os.Exit(1)
} else {
fmt.Println("✅ All required dependencies are installed!")
fmt.Println(" You're ready for iOS development with Wails!")
}
}
func checkCommand(args []string) bool {
if len(args) == 0 {
return false
}
cmd := exec.Command(args[0], args[1:]...)
cmd.Stdout = nil
cmd.Stderr = nil
err := cmd.Run()
return err == nil
}
func promptUser(question string) bool {
// Check if we're in a non-interactive environment
if os.Getenv("CI") != "" || os.Getenv("TASK_FORCE_YES") == "true" {
fmt.Printf("%s [y/N]: y (auto-accepted)\n", question)
return true
}
reader := bufio.NewReader(os.Stdin)
fmt.Printf("%s [y/N]: ", question)
response, err := reader.ReadString('\n')
if err != nil {
return false
}
response = strings.ToLower(strings.TrimSpace(response))
return response == "y" || response == "yes"
}