Add iOS build and run support for the DarkFi app

This pull request adds full iOS build and run support for the DarkFi app, including device, simulator and Xcode integration. It introduces ios-release  and ios-sim Makefile targets that build signed app bundles for devices and simulators.

Add SDK detection and Apple Silicon arch via xcrun / xcodebuild and improved error messages.

A new ios-xcode target generates a minimal DarkFi.xcodeproj using the external build system so the app can be archived and uploaded directly from Xcode.

The changes include an ios-Info.plist , updated entitlements and embedded.mobileprovision handling to fix crash-on-launch issues and allow fullscreen UI and arbitrary network loads.

iOS-specific path handling and ui_consts are added (with  #cfg(target_os = "ios")  and  HOME/Documents  logic) to avoid sandbox panics and unwritable home directory crashes while keeping Tor/arti working.

Asset copying is standardized so the assets content (including forest_720x1280 / 1920x1080 and locales) is laid out in the bundle in a way that matches the code’s VID_PATH / LOCALE_PATH expectations and macroquad ’s resource lookup on iOS.

Cargo dependencies are tuned for iOS by keeping required pieces like tor-dirmgr, excluding incompatible ones like tracing-android and ensuring no problematic iOS-only libraries cause link or runtime errors.
This commit is contained in:
nighthawk24
2025-12-19 17:11:16 -05:00
committed by darkfi
parent 7cd4aedce9
commit 7243212aee
13 changed files with 332 additions and 12 deletions

1
bin/app/Cargo.lock generated
View File

@@ -847,6 +847,7 @@ version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4"
dependencies = [
"sha2",
"tinyvec",
]

View File

@@ -91,6 +91,8 @@ tracing-android = "0.2.0"
# Required by Arti: tor-dirmgr
tor-dirmgr = { version="0.36.0", features=["static"] }
[target.'cfg(target_os = "ios")'.dependencies]
[target.'cfg(target_os = "windows")'.dependencies]
# Used by tor-dirmgr
#rusqlite = {version = "0.37.0", features = ["bundled"]}

View File

@@ -0,0 +1,6 @@
<?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>
</dict>
</plist>

View File

@@ -31,6 +31,95 @@ android: android-release
# Platform release builds
ios-release: $(SRC) fonts forest_720x1280 forest_1920x1080
# Create the app bundle structure
rm -rf DarkFi.app
mkdir -p DarkFi.app
# Build for iOS device (aarch64)
SDKROOT=$$(xcrun --sdk iphoneos --show-sdk-path 2>/dev/null); \
if [ -z "$$SDKROOT" ]; then \
SDKROOT=$$(xcodebuild -version -sdk iphoneos Path 2>/dev/null); \
fi; \
if [ -z "$$SDKROOT" ]; then \
echo "ERROR: Could not find iphoneos SDK."; \
echo "Current xcode-select path: $$(xcode-select -p)"; \
echo "Ensure Xcode is installed and selected: sudo xcode-select -s /Applications/Xcode.app"; \
exit 1; \
fi; \
export SDKROOT=$$SDKROOT; \
export IPHONEOS_DEPLOYMENT_TARGET=14.0; \
cargo build --target aarch64-apple-ios --release $(RELEASE_FEATURES)
# Copy the binary
cp target/aarch64-apple-ios/release/darkfi-app DarkFi.app/
# Copy resources
cp ios-Info.plist DarkFi.app/Info.plist
cp -r assets/lang DarkFi.app/
cp -r assets/forest_720x1280 DarkFi.app/
cp -r assets/forest_1920x1080 DarkFi.app/
cp assets/*.png DarkFi.app/ || true
cp embedded.mobileprovision DarkFi.app/ || echo "Warning: No provisioning profile found. Device builds will fail to launch."
cp ibm-plex-mono-regular.otf DarkFi.app/
cp NotoColorEmoji.ttf DarkFi.app/
# Sign the app bundle with entitlements
codesign --force --sign - --entitlements DarkFi.entitlements --timestamp=none DarkFi.app
# Create a dummy PkgInfo (optional but good practice)
echo "APPL????" > DarkFi.app/PkgInfo
@echo "DarkFi.app built for device (aarch64)."
ios-sim: $(SRC) fonts forest_720x1280 forest_1920x1080
# Create the app bundle structure
rm -rf DarkFi.app
mkdir -p DarkFi.app
# Build for iOS simulator (Detect Arch)
SDKROOT=$$(xcrun --sdk iphonesimulator --show-sdk-path 2>/dev/null); \
if [ -z "$$SDKROOT" ]; then \
SDKROOT=$$(xcodebuild -version -sdk iphonesimulator Path 2>/dev/null); \
fi; \
if [ -z "$$SDKROOT" ]; then \
echo "ERROR: Could not find iphonesimulator SDK."; \
echo "Current xcode-select path: $$(xcode-select -p)"; \
echo "Ensure Xcode is installed and selected: sudo xcode-select -s /Applications/Xcode.app"; \
exit 1; \
fi; \
export SDKROOT=$$SDKROOT; \
export IPHONEOS_DEPLOYMENT_TARGET=14.0; \
ARCH=$$(uname -m); \
if [ "$$ARCH" = "arm64" ]; then \
echo "Detected Apple Silicon (arm64). Building for aarch64-apple-ios-sim..."; \
cargo build --target aarch64-apple-ios-sim --release $(RELEASE_FEATURES); \
cp target/aarch64-apple-ios-sim/release/darkfi-app DarkFi.app/; \
else \
echo "Detected Intel (x86_64). Building for x86_64-apple-ios..."; \
cargo build --target x86_64-apple-ios --release $(RELEASE_FEATURES); \
cp target/x86_64-apple-ios/release/darkfi-app DarkFi.app/; \
fi
# Copy resources
cp ios-Info.plist DarkFi.app/Info.plist
cp -r assets/lang DarkFi.app/
cp -r assets/forest_720x1280 DarkFi.app/
cp -r assets/forest_1920x1080 DarkFi.app/
cp assets/*.png DarkFi.app/ || true
cp ibm-plex-mono-regular.otf DarkFi.app/
cp NotoColorEmoji.ttf DarkFi.app/
# Sign the app bundle with entitlements
codesign --force --sign - --entitlements DarkFi.entitlements --timestamp=none DarkFi.app
echo "APPL????" > DarkFi.app/PkgInfo
@echo "DarkFi.app built for simulator."
ios-xcode:
rm -rf DarkFi.xcodeproj
mkdir -p DarkFi.xcodeproj
# Generate unique IDs for the project file (24 chars)
sed -e "s/CONFIG_LIST_ID/$$(uuidgen | tr -d - | tr '[:lower:]' '[:upper:]' | cut -c 1-24)/g" \
-e "s/MAIN_GROUP_ID/$$(uuidgen | tr -d - | tr '[:lower:]' '[:upper:]' | cut -c 1-24)/g" \
-e "s/LEGACY_TARGET_ID/$$(uuidgen | tr -d - | tr '[:lower:]' '[:upper:]' | cut -c 1-24)/g" \
-e "s/PROJECT_ID/$$(uuidgen | tr -d - | tr '[:lower:]' '[:upper:]' | cut -c 1-24)/g" \
-e "s/PROJECT_CONFIG_LIST_ID/$$(uuidgen | tr -d - | tr '[:lower:]' '[:upper:]' | cut -c 1-24)/g" \
-e "s/DEBUG_CONFIG_ID/$$(uuidgen | tr -d - | tr '[:lower:]' '[:upper:]' | cut -c 1-24)/g" \
-e "s/RELEASE_CONFIG_ID/$$(uuidgen | tr -d - | tr '[:lower:]' '[:upper:]' | cut -c 1-24)/g" \
project.pbxproj.template > DarkFi.xcodeproj/project.pbxproj
@echo "Generated DarkFi.xcodeproj. Open it in Xcode and ensure the 'External Build Tool' configuration points to 'make' and target 'ios-release' in this directory."
macos-release: build-release
-mv darkfi-app darkfi-app.macos
macos-debug: build-debug
@@ -74,14 +163,14 @@ forest_1920x1080.zip:
assets/forest_1920x1080/000.qoi:
cd assets && unzip ../forest_1920x1080.zip
forest_1920x1080: forest_1920x1080.zip assets/forest_1920x1080/000.qoi
rm -fr assets/forest_729x1280/
# rm -fr assets/forest_729x1280/
forest_720x1280.zip:
wget -c https://codeberg.org/darkrenaissance/darkfi/raw/branch/data/forest_720x1280.zip
assets/forest_720x1280/000.qoi:
cd assets && unzip ../forest_720x1280.zip
forest_720x1280: forest_720x1280.zip assets/forest_720x1280/000.qoi
rm -fr assets/forest_1920x1080/
# rm -fr assets/forest_1920x1080/
# Developer targets
@@ -112,6 +201,9 @@ fmt:
clean:
podman run -v $(shell pwd):/root/dw -w /root/dw -t apk rm -fr target/
rm -fr target/
rm -f darkfi-app.apk
rm -rf DarkFi.app
rm -rf DarkFi.xcodeproj
.PHONY: all android cli clean

41
bin/app/ios-Info.plist Normal file
View File

@@ -0,0 +1,41 @@
<?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>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>darkfi-app</string>
<key>CFBundleIdentifier</key>
<string>fi.dark.app</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>DarkFi</string>
<key>CFBundleDisplayName</key>
<string>DarkFi</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UIRequiresFullScreen</key>
<true/>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
</dict>
</plist>

View File

@@ -0,0 +1,118 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objects = {
/* Begin PBXBuildFile section */
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
/* End PBXFileReference section */
/* Begin PBXGroup section */
MAIN_GROUP_ID = {
isa = PBXGroup;
children = (
);
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXLegacyTarget section */
LEGACY_TARGET_ID /* DarkFi */ = {
isa = PBXLegacyTarget;
buildArgumentsString = "ios-release";
buildConfigurationList = CONFIG_LIST_ID /* Build configuration list for PBXLegacyTarget "DarkFi" */;
buildPhases = (
);
buildToolPath = /usr/bin/make;
buildWorkingDirectory = "$(PROJECT_DIR)";
dependencies = (
);
name = "DarkFi (Make)";
passBuildSettingsInEnvironment = 1;
productName = DarkFi;
};
/* End PBXLegacyTarget section */
/* Begin PBXProject section */
PROJECT_ID /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 0720;
ORGANIZATIONNAME = "DarkFi";
};
buildConfigurationList = PROJECT_CONFIG_LIST_ID /* Build configuration list for PBXProject "DarkFi" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = English;
hasScannedForEncodings = 0;
knownRegions = (
en,
);
mainGroup = MAIN_GROUP_ID;
projectDirPath = "";
projectRoot = "";
targets = (
LEGACY_TARGET_ID /* DarkFi (Make) */,
);
};
/* End PBXProject section */
/* Begin XCBuildConfiguration section */
DEBUG_CONFIG_ID /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
COPY_PHASE_STRIP = NO;
GCC_DYNAMIC_NO_PIC = NO;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_SYMBOLS_PRIVATE_EXTERN = NO;
GCC_VERSION = com.apple.compilers.llvm.clang.1_0;
OTHER_CFLAGS = "";
};
name = Debug;
};
RELEASE_CONFIG_ID /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
COPY_PHASE_STRIP = YES;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
GCC_VERSION = com.apple.compilers.llvm.clang.1_0;
OTHER_CFLAGS = "";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
PROJECT_CONFIG_LIST_ID /* Build configuration list for PBXProject "DarkFi" */ = {
isa = XCConfigurationList;
buildConfigurations = (
DEBUG_CONFIG_ID /* Debug */,
RELEASE_CONFIG_ID /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
CONFIG_LIST_ID /* Build configuration list for PBXLegacyTarget "DarkFi" */ = {
isa = XCConfigurationList;
buildConfigurations = (
DEBUG_CONFIG_ID /* Debug */,
RELEASE_CONFIG_ID /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = PROJECT_ID /* Project object */;
}

View File

@@ -30,6 +30,11 @@ mod ui_consts {
pub const LOCALE_PATH: &str = "assets/lang/{locale}/{entry}";
}
#[cfg(target_os = "ios")]
mod ui_consts {
pub const LOCALE_PATH: &str = "lang/{locale}/{entry}";
}
pub use ui_consts::*;
static ENTRIES: &[&'static str] = &["app.ftl"];

View File

@@ -46,7 +46,7 @@ use crate::{
use super::{ColorScheme, COLOR_SCHEME};
#[cfg(any(target_os = "android", feature = "emulate-android"))]
#[cfg(any(target_os = "android", target_os = "ios", feature = "emulate-android"))]
mod android_ui_consts {
use crate::gfx::{Point, Rectangle};
@@ -99,7 +99,7 @@ mod android_ui_consts {
pub const ACTION_LABEL_POS: Point = Point::new(55., 50.);
}
#[cfg(target_os = "android")]
#[cfg(any(target_os = "android", target_os = "ios"))]
mod ui_consts {
pub use super::android_ui_consts::*;
}

View File

@@ -31,7 +31,7 @@ use crate::{
use super::{ColorScheme, CHANNELS, COLOR_SCHEME};
#[cfg(any(target_os = "android", feature = "emulate-android"))]
#[cfg(any(target_os = "android", target_os = "ios", feature = "emulate-android"))]
mod android_ui_consts {
pub const CHANNEL_LABEL_X: f32 = 40.;
pub const CHANNEL_LABEL_Y: f32 = 35.;
@@ -39,7 +39,7 @@ mod android_ui_consts {
pub const CHANNEL_LABEL_FONTSIZE: f32 = 44.;
}
#[cfg(target_os = "android")]
#[cfg(any(target_os = "android", target_os = "ios"))]
mod ui_consts {
pub use super::android_ui_consts::*;
}

View File

@@ -43,7 +43,7 @@ pub mod test;
const COLOR_SCHEME: ColorScheme = ColorScheme::DarkMode;
//const COLOR_SCHEME: ColorScheme = ColorScheme::PaperLight;
#[cfg(any(target_os = "android", feature = "emulate-android"))]
#[cfg(any(target_os = "android", target_os = "ios", feature = "emulate-android"))]
mod android_ui_consts {
pub const NETSTATUS_ICON_SIZE: f32 = 140.;
pub const SETTINGS_ICON_SIZE: f32 = 140.;
@@ -77,7 +77,53 @@ mod ui_consts {
}
}
#[cfg(not(target_os = "android"))]
#[cfg(target_os = "ios")]
mod ui_consts {
use std::path::PathBuf;
pub const VID_PATH: &str = "forest_720x1280/{frame}.qoi";
pub const VID_ASPECT_RATIO: f32 = 9. / 16.;
pub use super::android_ui_consts::*;
fn get_ios_docs_dir() -> PathBuf {
let home = std::env::var("HOME").unwrap_or_else(|_| ".".to_string());
PathBuf::from(home).join("Documents")
}
pub fn get_chatdb_path() -> PathBuf {
let path = get_ios_docs_dir().join("darkfi/app/chatdb");
if let Some(parent) = path.parent() {
let _ = std::fs::create_dir_all(parent);
}
path
}
pub fn get_first_time_filename() -> PathBuf {
let path = get_ios_docs_dir().join("darkfi/app/first_time");
if let Some(parent) = path.parent() {
let _ = std::fs::create_dir_all(parent);
}
path
}
pub fn get_window_scale_filename() -> PathBuf {
let path = get_ios_docs_dir().join("darkfi/app/window_scale");
if let Some(parent) = path.parent() {
let _ = std::fs::create_dir_all(parent);
}
path
}
pub fn get_settingsdb_path() -> PathBuf {
let path = get_ios_docs_dir().join("darkfi/app/settings");
if let Some(parent) = path.parent() {
let _ = std::fs::create_dir_all(parent);
}
path
}
}
#[cfg(not(any(target_os = "android", target_os = "ios")))]
mod desktop_paths {
use std::path::PathBuf;

View File

@@ -36,7 +36,7 @@ use std::{
sync::{Arc, Mutex},
};
#[cfg(any(target_os = "android", feature = "emulate-android"))]
#[cfg(any(target_os = "android", target_os = "ios", feature = "emulate-android"))]
mod android_ui_consts {
pub const SETTING_LABEL_X: f32 = 40.;
pub const SETTING_LABEL_LINESPACE: f32 = 140.;
@@ -59,7 +59,7 @@ mod android_ui_consts {
pub const BACKARROW_BG_W: f32 = 120.;
}
#[cfg(target_os = "android")]
#[cfg(any(target_os = "android", target_os = "ios"))]
mod ui_consts {
pub use super::android_ui_consts::*;
}

View File

@@ -33,14 +33,14 @@ use crate::{
const LIGHTMODE: bool = false;
#[cfg(target_os = "android")]
#[cfg(any(target_os = "android", target_os = "ios"))]
mod ui_consts {
//pub const CHATDB_PATH: &str = "/data/data/darkfi.app/chatdb/";
//pub const KING_PATH: &str = "king.png";
pub const VID_PATH: &str = "forest/forest_{frame}.png";
}
#[cfg(not(target_os = "android"))]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
mod ui_consts {
//pub const CHATDB_PATH: &str = "chatdb";
//pub const KING_PATH: &str = "assets/king.png";

View File

@@ -143,6 +143,15 @@ impl God {
}
}
#[cfg(target_os = "ios")]
{
let home = std::env::var("HOME").unwrap_or(".".to_string());
let doc_path = std::path::PathBuf::from(home).join("Documents");
unsafe {
std::env::set_var("HOME", doc_path);
}
}
let exe_path = std::env::current_exe().unwrap();
let basename = exe_path.parent().unwrap();
std::env::set_current_dir(basename).unwrap();