From e6a6c9fb4c14a375f1769b34754453172fec69b5 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Mon, 17 Mar 2025 22:14:07 +0100 Subject: [PATCH] Link iOS example with `rustc`, and avoid C trampoline (#14780) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Objective On iOS: - Allow `std` to do its runtime initialization. - Avoid requiring the user to specify linked libraries and framework in Xcode. - Reduce the amount of work that `#[bevy_main]` does - In the future we may also be able to eliminate the need for it on Android, cc @daxpedda. ## Solution We previously: - Exposed an `extern "C" fn main_rs` entry point. - Ran Cargo in a separate Xcode target as an external build system. - Imported that as a dependency of `bevy_mobile_example.app`. - Compiled a trampoline C file with Xcode that called `main_rs`. - Linked that via. Xcode. All of this is unnecessary; `rustc` is well capable of creating iOS executables, the trick is just to place it at the correct location for Xcode to understand it, namely `$TARGET_BUILD_DIR/$EXECUTABLE_PATH` (places it in `bevy_mobile_example.app/bevy_mobile_example`). Note: We might want to wait with the changes to `#[bevy_main]` until the problem is resolved on Android too, to make the migration easier. ## Testing Open the Xcode project, and build for an iOS target. --- ## Migration Guide **If you have been building your application for iOS:** Previously, the `#[bevy_main]` attribute created a `main_rs` entry point that most Xcode templates were using to run your Rust code from C. This was found to be unnecessary, as you can simply let Rust build your application as a binary, and run that directly. You have two options for dealing with this: If you've added further C code and Xcode customizations, or it makes sense for your use-case to continue link with Xcode, you can revert to the old behaviour by adding `#[no_mangle] extern "C" main_rs() { main() }` to your `main.rs`. Note that the old approach of linking a static library prevents the Rust standard library from doing runtime initialization, so certain functionality provided by `std` might be unavailable (stack overflow handlers, stdout/stderr flushing and other such functionality provided by the initialization routines). The other, preferred option is to remove your "compile" and "link" build phases, and instead replace it with a "run script" phase that invokes `cargo build --bin ...`, and moves the built binary to the Xcode path `$TARGET_BUILD_DIR/$EXECUTABLE_PATH`. An example of how to do this can be viewed at [INSERT LINK TO UPDATED EXAMPLE PROJECT]. To make the debugging experience in Xcode nicer after this, you might also want to consider either enabling `panic = "abort"` or to set a breakpoint on the `rust_panic` symbol. --------- Co-authored-by: François Mockers Co-authored-by: François Mockers --- .github/workflows/ci.yml | 2 +- crates/bevy_derive/src/bevy_main.rs | 9 - examples/mobile/Cargo.toml | 2 +- .../project.pbxproj | 195 ++---------------- examples/mobile/build_rust_deps.sh | 35 +++- examples/mobile/ios-src/bindings.h | 1 - examples/mobile/ios-src/main.m | 6 - examples/mobile/src/lib.rs | 6 +- examples/mobile/src/main.rs | 2 + 9 files changed, 52 insertions(+), 206 deletions(-) delete mode 100644 examples/mobile/ios-src/bindings.h delete mode 100644 examples/mobile/ios-src/main.m create mode 100644 examples/mobile/src/main.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b0f4b89bd2..29d6e2418c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -428,7 +428,7 @@ jobs: shell: bash run: | errors="" - for file in $(find examples tests -name '*.rs'); do + for file in $(find examples tests -name '*.rs' -not -path 'examples/mobile/*'); do if grep -q "use bevy_" "$file"; then errors+="ERROR: Detected internal Bevy import in $file\n" fi diff --git a/crates/bevy_derive/src/bevy_main.rs b/crates/bevy_derive/src/bevy_main.rs index a9339b1395..6481823ad4 100644 --- a/crates/bevy_derive/src/bevy_main.rs +++ b/crates/bevy_derive/src/bevy_main.rs @@ -20,15 +20,6 @@ pub fn bevy_main(_attr: TokenStream, item: TokenStream) -> TokenStream { main(); } - // SAFETY: `#[bevy_main]` should only be placed on a single `main` function - // TODO: Potentially make `bevy_main` and unsafe attribute as there is a safety - // guarantee required from the caller. - #[unsafe(no_mangle)] - #[cfg(target_os = "ios")] - extern "C" fn main_rs() { - main(); - } - #[allow(unused)] #input }) diff --git a/examples/mobile/Cargo.toml b/examples/mobile/Cargo.toml index d54c7deee3..35a540604e 100644 --- a/examples/mobile/Cargo.toml +++ b/examples/mobile/Cargo.toml @@ -9,7 +9,7 @@ license = "MIT OR Apache-2.0" [lib] name = "bevy_mobile_example" -crate-type = ["staticlib", "cdylib"] +crate-type = ["lib", "cdylib"] [dependencies] bevy = { path = "../../" } diff --git a/examples/mobile/bevy_mobile_example.xcodeproj/project.pbxproj b/examples/mobile/bevy_mobile_example.xcodeproj/project.pbxproj index 858081f701..8a931e3435 100644 --- a/examples/mobile/bevy_mobile_example.xcodeproj/project.pbxproj +++ b/examples/mobile/bevy_mobile_example.xcodeproj/project.pbxproj @@ -3,60 +3,21 @@ archiveVersion = 1; classes = { }; - objectVersion = 51; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ - 134866208A035F8615C99114 /* Metal.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 96A1E5B62F48B379829E8A0D /* Metal.framework */; }; - 2604C99FAB5A8322EDCABB9F /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FE71FBCAA714DB4F42459106 /* UIKit.framework */; }; - 442540D056ADB9AE61A0A590 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9F1B41978FA53999AA836D0F /* Security.framework */; }; - 55892F1396056740E1AF9685 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = AF7DE91055EBD05ED77E57F9 /* main.m */; }; - 55B7188F81C3C4183F81D3AE /* libc++.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = A39528EB2CCB182F5328223A /* libc++.tbd */; }; - 57CD6306253C7A940098CD4A /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 57CD6305253C7A940098CD4A /* AudioToolbox.framework */; }; 57CD630E253C80EC0098CD4A /* assets in Resources */ = {isa = PBXBuildFile; fileRef = 57CD630A253C7F5F0098CD4A /* assets */; }; 6ADF1AB92CCDA73A00AF5F8E /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6ADF1AB82CCDA73A00AF5F8E /* QuartzCore.framework */; }; /* End PBXBuildFile section */ -/* Begin PBXContainerItemProxy section */ - 26BF2C4863C966DABAB40DC8 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 8DBF1E2B5C613DA41701F6D9 /* Project object */; - proxyType = 1; - remoteGlobalIDString = D08AEBE0B1A9C9A7B8C7B33F; - remoteInfo = cargo_ios; - }; -/* End PBXContainerItemProxy section */ - /* Begin PBXFileReference section */ - 160DB77300A3F1806F024D47 /* bindings.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = bindings.h; sourceTree = ""; }; 55EAC02897847195D2F44C15 /* bevy_mobile_example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = bevy_mobile_example.app; sourceTree = BUILT_PRODUCTS_DIR; }; - 57CD6305253C7A940098CD4A /* AudioToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioToolbox.framework; path = System/Library/Frameworks/AudioToolbox.framework; sourceTree = SDKROOT; }; 57CD630A253C7F5F0098CD4A /* assets */ = {isa = PBXFileReference; lastKnownFileType = folder; name = assets; path = ../../../assets; sourceTree = ""; }; 6ADF1AB82CCDA73A00AF5F8E /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; }; 8EE7F1E3B0303533925D7E33 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; - 96A1E5B62F48B379829E8A0D /* Metal.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Metal.framework; path = System/Library/Frameworks/Metal.framework; sourceTree = SDKROOT; }; - 9F1B41978FA53999AA836D0F /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; }; - A39528EB2CCB182F5328223A /* libc++.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = "libc++.tbd"; path = "usr/lib/libc++.tbd"; sourceTree = SDKROOT; }; - AF7DE91055EBD05ED77E57F9 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; - FE71FBCAA714DB4F42459106 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; /* End PBXFileReference section */ -/* Begin PBXFrameworksBuildPhase section */ - D5A822CB2D6847BA8800BE4C /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 6ADF1AB92CCDA73A00AF5F8E /* QuartzCore.framework in Frameworks */, - 442540D056ADB9AE61A0A590 /* Security.framework in Frameworks */, - 134866208A035F8615C99114 /* Metal.framework in Frameworks */, - 2604C99FAB5A8322EDCABB9F /* UIKit.framework in Frameworks */, - 55B7188F81C3C4183F81D3AE /* libc++.tbd in Frameworks */, - 57CD6306253C7A940098CD4A /* AudioToolbox.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - /* Begin PBXGroup section */ 321F7D6A765B38E746C35105 /* Products */ = { isa = PBXGroup; @@ -70,9 +31,7 @@ isa = PBXGroup; children = ( 57CD630A253C7F5F0098CD4A /* assets */, - 160DB77300A3F1806F024D47 /* bindings.h */, 8EE7F1E3B0303533925D7E33 /* Info.plist */, - AF7DE91055EBD05ED77E57F9 /* main.m */, ); path = "ios-src"; sourceTree = ""; @@ -89,49 +48,23 @@ EB028409C2D0655412DA6E44 /* Frameworks */ = { isa = PBXGroup; children = ( - 6ADF1AB82CCDA73A00AF5F8E /* QuartzCore.framework */, - 57CD6305253C7A940098CD4A /* AudioToolbox.framework */, - A39528EB2CCB182F5328223A /* libc++.tbd */, - 96A1E5B62F48B379829E8A0D /* Metal.framework */, - 9F1B41978FA53999AA836D0F /* Security.framework */, - FE71FBCAA714DB4F42459106 /* UIKit.framework */, ); name = Frameworks; sourceTree = ""; }; /* End PBXGroup section */ -/* Begin PBXLegacyTarget section */ - D08AEBE0B1A9C9A7B8C7B33F /* cargo_ios */ = { - isa = PBXLegacyTarget; - buildArgumentsString = build_rust_deps.sh; - buildConfigurationList = AA00A0CFDB11F37F2BA3FC2E /* Build configuration list for PBXLegacyTarget "cargo_ios" */; - buildPhases = ( - FE045B3D04D57B713A565FF8 /* Sources */, - ); - buildToolPath = /bin/sh; - buildWorkingDirectory = .; - dependencies = ( - ); - name = cargo_ios; - passBuildSettingsInEnvironment = 1; - productName = cargo_ios; - }; -/* End PBXLegacyTarget section */ - /* Begin PBXNativeTarget section */ 3BDB8152E4962373181B4FE5 /* bevy_mobile_example */ = { isa = PBXNativeTarget; buildConfigurationList = E714A1AEAAE517C348B5BD27 /* Build configuration list for PBXNativeTarget "bevy_mobile_example" */; buildPhases = ( - 9F13800790AD9DBC2BC0F116 /* Sources */, - D5A822CB2D6847BA8800BE4C /* Frameworks */, + ECFC92D02C6F14FA0095E5B1 /* Rust */, 57CD630D253C80E60098CD4A /* Resources */, ); buildRules = ( ); dependencies = ( - 19D4B9C22ADC6705B5132B4C /* PBXTargetDependency */, ); name = bevy_mobile_example; productName = bevy_mobile_example; @@ -158,7 +91,6 @@ projectRoot = ""; targets = ( 3BDB8152E4962373181B4FE5 /* bevy_mobile_example */, - D08AEBE0B1A9C9A7B8C7B33F /* cargo_ios */, ); }; /* End PBXProject section */ @@ -174,31 +106,29 @@ }; /* End PBXResourcesBuildPhase section */ -/* Begin PBXSourcesBuildPhase section */ - 9F13800790AD9DBC2BC0F116 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 55892F1396056740E1AF9685 /* main.m in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - FE045B3D04D57B713A565FF8 /* Sources */ = { - isa = PBXSourcesBuildPhase; +/* Begin PBXShellScriptBuildPhase section */ + ECFC92D02C6F14FA0095E5B1 /* Rust */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); + inputFileListPaths = ( + ); + inputPaths = ( + "$(SRCROOT)/build_rust_deps.sh", + ); + name = Rust; + outputFileListPaths = ( + ); + outputPaths = ( + $TARGET_BUILD_DIR/$EXECUTABLE_PATH, + ); runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "./build_rust_deps.sh\n"; }; -/* End PBXSourcesBuildPhase section */ - -/* Begin PBXTargetDependency section */ - 19D4B9C22ADC6705B5132B4C /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = D08AEBE0B1A9C9A7B8C7B33F /* cargo_ios */; - targetProxy = 26BF2C4863C966DABAB40DC8 /* PBXContainerItemProxy */; - }; -/* End PBXTargetDependency section */ +/* End PBXShellScriptBuildPhase section */ /* Begin XCBuildConfiguration section */ 4AD7BC6FDD56FF18FA6DA7D7 /* Debug */ = { @@ -271,33 +201,8 @@ CODE_SIGN_IDENTITY = ""; DEVELOPMENT_TEAM = ""; ENABLE_BITCODE = NO; - HEADER_SEARCH_PATHS = ( - "$(inherited)", - "ios-src/", - ); INFOPLIST_FILE = "ios-src/Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 12.0; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - "LIBRARY_SEARCH_PATHS[sdk=iphoneos*][arch=arm64]" = ( - "$(inherited)", - "../../target/aarch64-apple-ios/release", - ); - "LIBRARY_SEARCH_PATHS[sdk=iphonesimulator*][arch=arm64]" = ( - "$(inherited)", - "../../target/aarch64-apple-ios-sim/release", - ); - "LIBRARY_SEARCH_PATHS[sdk=iphonesimulator*][arch=x86_64]" = ( - "$(inherited)", - "../../target/x86_64-apple-ios/release", - ); - OTHER_LDFLAGS = ( - "$(inherited)", - "-lbevy_mobile_example", - "-lc++abi", - ); PRODUCT_BUNDLE_IDENTIFIER = org.bevyengine.example; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; @@ -357,19 +262,6 @@ }; name = Release; }; - 8265913A25816D964A847F1B /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = "com.rust.cargo-ios"; - SDKROOT = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Debug; - }; A2D5B73DD30D562B6F366526 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -379,52 +271,14 @@ CODE_SIGN_IDENTITY = ""; DEVELOPMENT_TEAM = ""; ENABLE_BITCODE = NO; - HEADER_SEARCH_PATHS = ( - "$(inherited)", - "ios-src/", - ); INFOPLIST_FILE = "ios-src/Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 12.0; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - "LIBRARY_SEARCH_PATHS[sdk=iphoneos*][arch=arm64]" = ( - "$(inherited)", - "../../target/aarch64-apple-ios/debug", - ); - "LIBRARY_SEARCH_PATHS[sdk=iphonesimulator*][arch=arm64]" = ( - "$(inherited)", - "../../target/aarch64-apple-ios-sim/debug", - ); - "LIBRARY_SEARCH_PATHS[sdk=iphonesimulator*][arch=x86_64]" = ( - "$(inherited)", - "../../target/x86_64-apple-ios/debug", - ); - OTHER_LDFLAGS = ( - "$(inherited)", - "-lbevy_mobile_example", - "-lc++abi", - ); PRODUCT_BUNDLE_IDENTIFIER = org.bevyengine.example; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; - FEA9B18D9236F9F6DC6DF799 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = "com.rust.cargo-ios"; - SDKROOT = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Release; - }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -437,15 +291,6 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Debug; }; - AA00A0CFDB11F37F2BA3FC2E /* Build configuration list for PBXLegacyTarget "cargo_ios" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 8265913A25816D964A847F1B /* Debug */, - FEA9B18D9236F9F6DC6DF799 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Debug; - }; E714A1AEAAE517C348B5BD27 /* Build configuration list for PBXNativeTarget "bevy_mobile_example" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/examples/mobile/build_rust_deps.sh b/examples/mobile/build_rust_deps.sh index e232648a2b..79eeddb28d 100755 --- a/examples/mobile/build_rust_deps.sh +++ b/examples/mobile/build_rust_deps.sh @@ -6,30 +6,35 @@ set -eux PATH=$PATH:$HOME/.cargo/bin +PROFILE=debug RELFLAG= if [[ "$CONFIGURATION" != "Debug" ]]; then + PROFILE=release RELFLAG=--release fi set -euvx -if [[ -n "${DEVELOPER_SDK_DIR:-}" ]]; then - # Assume we're in Xcode, which means we're probably cross-compiling. - # In this case, we need to add an extra library search path for build scripts and proc-macros, - # which run on the host instead of the target. - # (macOS Big Sur does not have linkable libraries in /usr/lib/.) - export LIBRARY_PATH="${DEVELOPER_SDK_DIR}/MacOSX.sdk/usr/lib:${LIBRARY_PATH:-}" -fi - # add homebrew bin path, as it's the most commonly used package manager on macOS # this is needed for cmake on apple arm processors as it's not available by default export PATH="$PATH:/opt/homebrew/bin" +# Make Cargo output cache files in Xcode's directories +export CARGO_TARGET_DIR="$DERIVED_FILE_DIR/cargo" + +# Xcode places `/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin` +# at the front of the path, with makes the build fail with `ld: library 'System' not found`, upstream issue: +# . +# +# Work around it by resetting the path, so that we use the system `cc`. +export PATH="/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:$PATH" + IS_SIMULATOR=0 if [ "${LLVM_TARGET_TRIPLE_SUFFIX-}" = "-simulator" ]; then IS_SIMULATOR=1 fi +EXECUTABLES= for arch in $ARCHS; do case "$arch" in x86_64) @@ -40,16 +45,24 @@ for arch in $ARCHS; do # Intel iOS simulator export CFLAGS_x86_64_apple_ios="-target x86_64-apple-ios" - cargo rustc --crate-type staticlib --lib $RELFLAG --target x86_64-apple-ios + TARGET=x86_64-apple-ios ;; arm64) if [ $IS_SIMULATOR -eq 0 ]; then # Hardware iOS targets - cargo rustc --crate-type staticlib --lib $RELFLAG --target aarch64-apple-ios + TARGET=aarch64-apple-ios else # M1 iOS simulator - cargo rustc --crate-type staticlib --lib $RELFLAG --target aarch64-apple-ios-sim + TARGET=aarch64-apple-ios-sim fi esac + + cargo build $RELFLAG --target $TARGET --bin bevy_mobile_example + + # Collect the executables + EXECUTABLES="$EXECUTABLES $DERIVED_FILE_DIR/cargo/$TARGET/$PROFILE/bevy_mobile_example" done + +# Combine executables, and place them at the output path excepted by Xcode +lipo -create -output "$TARGET_BUILD_DIR/$EXECUTABLE_PATH" $EXECUTABLES diff --git a/examples/mobile/ios-src/bindings.h b/examples/mobile/ios-src/bindings.h deleted file mode 100644 index a705f03b13..0000000000 --- a/examples/mobile/ios-src/bindings.h +++ /dev/null @@ -1 +0,0 @@ -void main_rs(void); diff --git a/examples/mobile/ios-src/main.m b/examples/mobile/ios-src/main.m deleted file mode 100644 index 7d152fb790..0000000000 --- a/examples/mobile/ios-src/main.m +++ /dev/null @@ -1,6 +0,0 @@ -#import "bindings.h" - -int main() { - main_rs(); - return 0; -} diff --git a/examples/mobile/src/lib.rs b/examples/mobile/src/lib.rs index cd73f084e2..c5df6ee3c7 100644 --- a/examples/mobile/src/lib.rs +++ b/examples/mobile/src/lib.rs @@ -9,9 +9,11 @@ use bevy::{ winit::WinitSettings, }; -// the `bevy_main` proc_macro generates the required boilerplate for iOS and Android +// the `bevy_main` proc_macro generates the required boilerplate for Android #[bevy_main] -fn main() { +/// The entry point for the application. Is `pub` so that it can be used from +/// `main.rs`. +pub fn main() { let mut app = App::new(); app.add_plugins( DefaultPlugins diff --git a/examples/mobile/src/main.rs b/examples/mobile/src/main.rs new file mode 100644 index 0000000000..9251b0b4a0 --- /dev/null +++ b/examples/mobile/src/main.rs @@ -0,0 +1,2 @@ +//! The entry point for iOS applications. +use bevy_mobile_example::main;