Initial commit
This commit is contained in:
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<?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>CFBundleDisplayName</key>
|
||||
<string>OPIc Flow (Dev)</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>opic-flow</string>
|
||||
<key>CFBundleGetInfoString</key>
|
||||
<string>Wails 3 + Vue TypeScript desktop application</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>app.opicflow.desktop.dev</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>OPIc Flow (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>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
<key>NSAllowsArbitraryLoads</key>
|
||||
<true/>
|
||||
<key>NSAllowsLocalNetworking</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string>(c) 2026, OPIc Flow</string>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
<array>
|
||||
<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>WailsDevelopmentMode</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,54 @@
|
||||
<?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>CFBundleDisplayName</key>
|
||||
<string>OPIc Flow</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>opic-flow</string>
|
||||
<key>CFBundleGetInfoString</key>
|
||||
<string>Wails 3 + Vue TypeScript desktop application</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>app.opicflow.desktop</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>OPIc Flow</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>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
<key>NSAllowsArbitraryLoads</key>
|
||||
<false/>
|
||||
<key>NSAllowsLocalNetworking</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string>(c) 2026, OPIc Flow</string>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
<array>
|
||||
<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>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -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="OPIc Flow" 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="AI voice practice and feedback for the OPIc speaking test" 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>
|
||||
@@ -0,0 +1,365 @@
|
||||
version: '3'
|
||||
|
||||
includes:
|
||||
common: ../Taskfile.yml
|
||||
|
||||
# Platform selection:
|
||||
# task ios:package -> simulator bundle (ad-hoc signed)
|
||||
# task ios:package IOS_PLATFORM=device CODESIGN_IDENTITY="Apple Development: ..." \
|
||||
# [PROVISIONING_PROFILE=path.mobileprovision]
|
||||
# -> device bundle (real signing)
|
||||
# task ios:package:ipa IOS_PLATFORM=device ... -> .ipa for distribution/install
|
||||
# For automatically managed signing & provisioning, use `task ios:xcode` and
|
||||
# build from the generated Xcode project instead.
|
||||
vars:
|
||||
BUNDLE_ID: '{{.BUNDLE_ID | default "com.wails.app"}}'
|
||||
IOS_PLATFORM: '{{.IOS_PLATFORM | default "simulator"}}'
|
||||
MIN_IOS_VERSION: '{{.MIN_IOS_VERSION | default "15.0"}}'
|
||||
IOS_SDK: '{{if eq .IOS_PLATFORM "device"}}iphoneos{{else}}iphonesimulator{{end}}'
|
||||
IOS_TARGET: 'arm64-apple-ios{{.MIN_IOS_VERSION}}{{if ne .IOS_PLATFORM "device"}}-simulator{{end}}'
|
||||
IOS_VERSION_MIN_FLAG: '{{if eq .IOS_PLATFORM "device"}}-miphoneos-version-min={{.MIN_IOS_VERSION}}{{else}}-mios-simulator-version-min={{.MIN_IOS_VERSION}}{{end}}'
|
||||
# "-" is ad-hoc signing: fine for the simulator, rejected by real devices.
|
||||
CODESIGN_IDENTITY: '{{.CODESIGN_IDENTITY | default "-"}}'
|
||||
# Optional path to a .mobileprovision embedded into device bundles
|
||||
PROVISIONING_PROFILE: '{{.PROVISIONING_PROFILE | default ""}}'
|
||||
# Optional entitlements applied at codesign time (generated default below)
|
||||
ENTITLEMENTS_FILE: '{{.ENTITLEMENTS_FILE | default "build/ios/entitlements.plist"}}'
|
||||
# SDK_PATH is computed lazily at task-level to avoid errors on non-macOS systems
|
||||
|
||||
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: common:build:frontend
|
||||
vars:
|
||||
BUILD_FLAGS:
|
||||
ref: .BUILD_FLAGS
|
||||
PRODUCTION:
|
||||
ref: .PRODUCTION
|
||||
cmds:
|
||||
- echo "Building iOS app {{.APP_NAME}} ({{.IOS_PLATFORM}})..."
|
||||
- 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 {{.IOS_SDK}} --show-sdk-path
|
||||
env:
|
||||
GOOS: ios
|
||||
CGO_ENABLED: 1
|
||||
GOARCH: '{{.ARCH | default "arm64"}}'
|
||||
PRODUCTION: '{{.PRODUCTION | default "false"}}'
|
||||
CGO_CFLAGS: '-isysroot {{.SDK_PATH}} -target {{.IOS_TARGET}} {{.IOS_VERSION_MIN_FLAG}}'
|
||||
CGO_LDFLAGS: '-isysroot {{.SDK_PATH}} -target {{.IOS_TARGET}}'
|
||||
|
||||
package:
|
||||
summary: Packages a production build of the application into a `.app` bundle
|
||||
deps:
|
||||
- task: build
|
||||
vars:
|
||||
PRODUCTION: "true"
|
||||
cmds:
|
||||
- task: compile:ios:link
|
||||
- task: create:app:bundle
|
||||
|
||||
package:ipa:
|
||||
summary: Packages a production build into an .ipa (requires IOS_PLATFORM=device and a real CODESIGN_IDENTITY)
|
||||
deps:
|
||||
- task: package
|
||||
cmds:
|
||||
- rm -rf "{{.BIN_DIR}}/Payload" "{{.BIN_DIR}}/{{.APP_NAME}}.ipa"
|
||||
- mkdir -p "{{.BIN_DIR}}/Payload"
|
||||
- cp -R "{{.BIN_DIR}}/{{.APP_NAME}}.app" "{{.BIN_DIR}}/Payload/"
|
||||
- cd "{{.BIN_DIR}}" && zip -qry "{{.APP_NAME}}.ipa" Payload
|
||||
- rm -rf "{{.BIN_DIR}}/Payload"
|
||||
- echo "Created {{.BIN_DIR}}/{{.APP_NAME}}.ipa"
|
||||
|
||||
# Link the Go c-archive with main.m into the final executable.
|
||||
# Used by both the dev `run` flow and `package`.
|
||||
compile:ios:link:
|
||||
internal: true
|
||||
summary: Link the iOS executable from the Go archive and main.m
|
||||
vars:
|
||||
SDK_PATH:
|
||||
sh: xcrun --sdk {{.IOS_SDK}} --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 {{.IOS_SDK}} clang \
|
||||
-target {{.IOS_TARGET}} \
|
||||
-isysroot {{.SDK_PATH}} \
|
||||
-framework Foundation -framework UIKit -framework WebKit \
|
||||
-framework Security -framework CoreFoundation \
|
||||
-framework UniformTypeIdentifiers \
|
||||
-framework LocalAuthentication -framework UserNotifications \
|
||||
-framework AVFoundation \
|
||||
-framework CoreLocation -framework CoreMotion -framework SystemConfiguration \
|
||||
-lresolv \
|
||||
-o "{{.BIN_DIR}}/{{.APP_NAME | lower}}" \
|
||||
"$MAIN_M" -Wl,-force_load,"{{.BIN_DIR}}/{{.APP_NAME}}.a"
|
||||
|
||||
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 | lower}}" "{{.BIN_DIR}}/{{.APP_NAME}}.app/"
|
||||
- cp build/ios/Info.plist "{{.BIN_DIR}}/{{.APP_NAME}}.app/"
|
||||
- |
|
||||
# Embed the provisioning profile for device builds when provided
|
||||
if [ "{{.IOS_PLATFORM}}" = "device" ] && [ -n "{{.PROVISIONING_PROFILE}}" ]; then
|
||||
cp "{{.PROVISIONING_PROFILE}}" "{{.BIN_DIR}}/{{.APP_NAME}}.app/embedded.mobileprovision"
|
||||
fi
|
||||
- |
|
||||
# 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 {{.IOS_SDK}} \
|
||||
--minimum-deployment-target {{.MIN_IOS_VERSION}} \
|
||||
--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
|
||||
- |
|
||||
# Sign the bundle. Ad-hoc ("-") works on the simulator only; device
|
||||
# builds need a real identity (and usually entitlements + profile).
|
||||
# Entitlements are only applied to device builds - simulator bundles
|
||||
# must not carry them (SpringBoard refuses to launch the app).
|
||||
if [ "{{.IOS_PLATFORM}}" = "device" ] && [ -f "{{.ENTITLEMENTS_FILE}}" ]; then
|
||||
codesign --force --sign "{{.CODESIGN_IDENTITY}}" --entitlements "{{.ENTITLEMENTS_FILE}}" "{{.BIN_DIR}}/{{.APP_NAME}}.app"
|
||||
else
|
||||
codesign --force --sign "{{.CODESIGN_IDENTITY}}" "{{.BIN_DIR}}/{{.APP_NAME}}.app"
|
||||
fi
|
||||
|
||||
deploy-simulator:
|
||||
summary: Deploy to iOS Simulator
|
||||
deps: [package]
|
||||
cmds:
|
||||
- |
|
||||
# Read the real bundle id from the built bundle (it comes from
|
||||
# build/config.yml, not the BUNDLE_ID var, so it varies per project).
|
||||
APP="{{.BIN_DIR}}/{{.APP_NAME}}.app"
|
||||
BUNDLE_ID=$(/usr/libexec/PlistBuddy -c 'Print :CFBundleIdentifier' "$APP/Info.plist")
|
||||
xcrun simctl terminate booted "$BUNDLE_ID" 2>/dev/null || true
|
||||
xcrun simctl uninstall booted "$BUNDLE_ID" 2>/dev/null || true
|
||||
xcrun simctl install booted "$APP"
|
||||
xcrun simctl launch booted "$BUNDLE_ID"
|
||||
|
||||
deploy-device:
|
||||
summary: Install and launch on a connected device (requires IOS_PLATFORM=device build and real signing)
|
||||
cmds:
|
||||
- |
|
||||
DEVICE="{{.DEVICE_ID}}"
|
||||
if [ -z "$DEVICE" ]; then
|
||||
DEVICE=$(xcrun devicectl list devices --hide-headers 2>/dev/null | awk 'NR==1 {print $3}')
|
||||
fi
|
||||
if [ -z "$DEVICE" ]; then
|
||||
echo "No connected device found. Pass DEVICE_ID=<identifier> (see: xcrun devicectl list devices)"
|
||||
exit 1
|
||||
fi
|
||||
xcrun devicectl device install app --device "$DEVICE" "{{.BIN_DIR}}/{{.APP_NAME}}.app"
|
||||
BUNDLE_ID=$(/usr/libexec/PlistBuddy -c 'Print :CFBundleIdentifier' "{{.BIN_DIR}}/{{.APP_NAME}}.app/Info.plist")
|
||||
xcrun devicectl device process launch --device "$DEVICE" "$BUNDLE_ID"
|
||||
|
||||
compile:ios:
|
||||
summary: Compile the iOS executable from Go archive and main.m
|
||||
deps:
|
||||
- task: build
|
||||
cmds:
|
||||
- task: compile:ios:link
|
||||
|
||||
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 {{.IOS_SDK}} --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 {{.IOS_TARGET}} {{.IOS_VERSION_MIN_FLAG}}'
|
||||
CGO_LDFLAGS: '-isysroot {{.SDK_PATH}} -target {{.IOS_TARGET}}'
|
||||
|
||||
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}')
|
||||
# Use the newest iPhone device type this Xcode knows about
|
||||
DEVICE_TYPE=$(xcrun simctl list devicetypes | grep -oE "iPhone [0-9][^(]*" | tail -1 | xargs)
|
||||
DEVICE_ID=$(xcrun simctl create "Wails iPhone" "${DEVICE_TYPE:-iPhone 15}" "$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 {{.MIN_IOS_VERSION}} \
|
||||
--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"
|
||||
- |
|
||||
# The dev bundle id comes from build/ios/Info.dev.plist (derived from
|
||||
# build/config.yml), so read it from the built bundle rather than
|
||||
# assuming a com.wails.* prefix.
|
||||
DEV_APP="{{.BIN_DIR}}/{{.APP_NAME}}.dev.app"
|
||||
BUNDLE_ID=$(/usr/libexec/PlistBuddy -c 'Print :CFBundleIdentifier' "$DEV_APP/Info.plist")
|
||||
xcrun simctl terminate booted "$BUNDLE_ID" 2>/dev/null || true
|
||||
xcrun simctl uninstall booted "$BUNDLE_ID" 2>/dev/null || true
|
||||
xcrun simctl install booted "$DEV_APP"
|
||||
xcrun simctl launch booted "$BUNDLE_ID"
|
||||
|
||||
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/"'
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
<?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>get-task-allow</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -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
|
||||
@@ -0,0 +1,22 @@
|
||||
//go:build ios
|
||||
// Minimal bootstrap: delegate comes from Go archive (WailsAppDelegate)
|
||||
#import <UIKit/UIKit.h>
|
||||
#include <stdio.h>
|
||||
|
||||
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);
|
||||
|
||||
// Call UIApplicationMain IMMEDIATELY and start NOTHING else here. Do not
|
||||
// start the Go runtime yet: starting it concurrently with UIApplicationMain
|
||||
// intermittently corrupts the FrontBoard launch handshake on a physical
|
||||
// device, so the app delegate's didFinishLaunchingWithOptions never fires
|
||||
// (blank cold launch / 0x8BADF00D). Instead, the WailsAppDelegate (provided
|
||||
// by the Go archive) starts the Go runtime itself from
|
||||
// didFinishLaunchingWithOptions — i.e. only AFTER UIKit has delivered the
|
||||
// launch — so the runtime never races the launch handshake.
|
||||
return UIApplicationMain(argc, argv, nil, @"WailsAppDelegate");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
//go:build ios
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"C"
|
||||
)
|
||||
|
||||
// For iOS builds we export a function the WailsAppDelegate can call.
|
||||
// This wrapper keeps the user's main.go unmodified.
|
||||
|
||||
// WailsIOSMain runs the user's main() (application.New / app.Run). It is invoked
|
||||
// by the WailsAppDelegate from didFinishLaunchingWithOptions — i.e. only AFTER
|
||||
// UIKit has launched — on a BACKGROUND thread, so the Go runtime never starts
|
||||
// concurrently with UIApplicationMain (that race intermittently corrupts the
|
||||
// FrontBoard launch handshake on a physical device → blank cold launch /
|
||||
// scene-create watchdog 0x8BADF00D). Keeping all app setup off the OS main
|
||||
// thread also leaves UIApplicationMain unobstructed on the main thread.
|
||||
//
|
||||
//export WailsIOSMain
|
||||
func WailsIOSMain() {
|
||||
main()
|
||||
}
|
||||
@@ -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 /* OPIc Flow.a in Frameworks */ = {isa = PBXBuildFile; fileRef = C0DEBEEF0000000000000107 /* OPIc Flow.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 /* OPIc Flow.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "OPIc Flow.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 /* OPIc Flow.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = "OPIc Flow.a"; path = "../../../bin/OPIc Flow.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 /* OPIc Flow.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 /* OPIc Flow.a */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
C0DEBEEF0000000000000040 /* OPIc Flow */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = C0DEBEEF0000000000000070 /* Build configuration list for PBXNativeTarget "OPIc Flow" */;
|
||||
buildPhases = (
|
||||
C0DEBEEF0000000000000055 /* Prebuild: Wails Go Archive */,
|
||||
C0DEBEEF0000000000000050 /* Sources */,
|
||||
C0DEBEEF0000000000000056 /* Frameworks */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = "OPIc Flow";
|
||||
productName = "OPIc Flow";
|
||||
productReference = C0DEBEEF0000000000000004 /* OPIc Flow.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
C0DEBEEF0000000000000060 /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastUpgradeCheck = 1500;
|
||||
ORGANIZATIONNAME = "OPIc Flow";
|
||||
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 /* OPIc Flow */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
C0DEBEEF0000000000000056 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
C0DEBEEF00000000000000F7 /* OPIc Flow.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}/../../..\"\nPLATFORM=${PLATFORM_NAME:-iphonesimulator}\nSDK_PATH=${SDKROOT:-$(xcrun --sdk ${PLATFORM} --show-sdk-path)}\nif [ ${PLATFORM} = iphonesimulator ]; then GO_TARGET=arm64-apple-ios15.0-simulator; MIN_FLAG=-mios-simulator-version-min=15.0; else GO_TARGET=arm64-apple-ios15.0; MIN_FLAG=-miphoneos-version-min=15.0; fi\nexport GOOS=ios\nexport GOARCH=arm64\nexport CGO_ENABLED=1\nexport CGO_CFLAGS=\"-isysroot ${SDK_PATH} -target ${GO_TARGET} ${MIN_FLAG}\"\nexport CGO_LDFLAGS=\"-isysroot ${SDK_PATH} -target ${GO_TARGET}\"\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# Always rebuild the Go c-archive; Go's build cache keeps this fast\n# and avoids stale binaries when .go sources change.\necho \"Building Go c-archive...\"\ngo build -buildmode=c-archive -overlay build/ios/xcode/overlay.json -o \"bin/OPIc Flow.a\"\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 = "app.opicflow.desktop";
|
||||
PRODUCT_NAME = "OPIc Flow";
|
||||
CODE_SIGNING_ALLOWED = NO;
|
||||
SDKROOT = iphoneos;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
C0DEBEEF00000000000000A0 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
INFOPLIST_FILE = main/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "app.opicflow.desktop";
|
||||
PRODUCT_NAME = "OPIc Flow";
|
||||
CODE_SIGNING_ALLOWED = NO;
|
||||
SDKROOT = iphoneos;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
C0DEBEEF0000000000000070 /* Build configuration list for PBXNativeTarget "OPIc Flow" */ = {
|
||||
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 */;
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
Reference in New Issue
Block a user