I an building a c archive in my iOS project using following:
GOOS=ios GOARCH=arm64 CGO_ENABLED=1 SDK=iphonesimulator CGO_CFLAGS="-fembed-bitcode" CC=pwd/clangwrap.sh go build -buildmode=c-archive -o libuplink.a
Clangwrap.sh looks like this
#!/bin/sh
# go/clangwrap.sh
SDK_PATH=`xcrun --sdk $SDK --show-sdk-path`
CLANG=`xcrun --sdk $SDK --find clang`
if [ "$GOARCH" == "amd64" ]; then
CARCH="x86_64"
elif [ "$GOARCH" == "arm64" ]; then
CARCH="arm64"
fi
exec $CLANG -arch $CARCH -isysroot $SDK_PATH -mios-version-min=10.0 "$#"
When I link it up in XCode and attempt to run with simulator however, I can only run it on the device itself:
building for iOS Simulator, but linking in object file built for iOS ... for architecture arm64
How do I target the simulator for a go build for a static library that's used in Swift project?
Requirements
Create a static library for the iPhone simulator
Use Apple Silicon instead of Intel simulator
Ability to specific minimum version
TL;DR
You could do something similar to Xcode if you choose a simulator as run destination.
So basically use something like -target arm64-apple-ios16.2-simulator instead of -arch arm64. Also omit -mios-version-min=10.0, since the actual minimal version is encoded in the -target (e.g. 16.2), which takes precedence (the correct option for the simulator would be -miphonesimulator-version-min anyway).
Then as CGO_LDFLAGS also specify the -target option plus -syslibroot with the path to the SDK.
Your build scripts tweaked a bit, it might look something like this:
This specifies the simulator as the target and the minimum version is 15.
build.sh
#!/bin/sh
export GOOS=ios
export GOARCH=arm64
export CGO_ENABLED=1
export SDK=iphonesimulator
export CGO_CFLAGS="-fembed-bitcode"
export MIN_VERSION=15
. ./target.sh
export CGO_LDFLAGS="-target ${TARGET} -syslibroot \"${SDK_PATH}\""
CC="$(pwd)/clangwrap.sh"
export CC
go build -buildmode=c-archive -o libuplink.a
target.sh
#!/bin/sh
SDK_PATH=$(xcrun --sdk "$SDK" --show-sdk-path)
export SDK_PATH
if [ "$GOARCH" = "amd64" ]; then
CARCH="x86_64"
elif [ "$GOARCH" = "arm64" ]; then
CARCH="arm64"
fi
if [ "$SDK" = "iphoneos" ]; then
export TARGET="$CARCH-apple-ios$MIN_VERSION"
elif [ "$SDK" = "iphonesimulator" ]; then
export TARGET="$CARCH-apple-ios$MIN_VERSION-simulator"
fi
clangwrap.sh
The clangwrap.sh then simplifies to:
#!/bin/zsh
CLANG=$(xcrun --sdk "$SDK" --find clang)
exec "$CLANG" -target "$TARGET" -isysroot "$SDK_PATH" "$#"
Details
Different SDKs
Different SDKs must be specified for an iOS device and the iPhone simulator. You can find them next to the other platforms supported by Xcode
under /Applications/Xcode.app/Contents/Developer/Platforms. For example, in Xcode 14.2, among others, there is an iPhoneOS platform with an iPhoneOS16.2.sdk and an iPhoneSimulator platform with iPhoneSimulator16.2.sdk.
There is this interesting post from an Apple employee in the Apple developer forum: https://developer.apple.com/forums/thread/673387#662260022
To check a generated static library to display the load commands, you can call:
otool -l libuplink.a
A static library generated for use with an Apple Silicon simulator should display something like the following:
...
Load command 1
cmd LC_BUILD_VERSION
cmdsize 24
platform 7
minos 15.0
sdk 16.2
...
Note: platform 7 denotes the simulator, minos the minimum deployment target, and sdk the actual SDK version used.
See the section in the include file loader.h that reads:
/* Known values for the above platform field. */
#define PLATFORM_UNKNOWN 0
#define PLATFORM_ANY 0xFFFFFF
#define PLATFORM_MACOS 1
#define PLATFORM_IOS 2
#define PLATFORM_TVOS 3
#define PLATFORM_WATCHOS 4
#define PLATFORM_BRIDGEOS 5
#define PLATFORM_MACCATALYST 6
#define PLATFORM_IOSSIMULATOR 7
#define PLATFORM_TVOSSIMULATOR 8
#define PLATFORM_WATCHOSSIMULATOR 9
#define PLATFORM_DRIVERKIT 10
You can view them yourself on your system as follows:
cat `xcrun --sdk iphonesimulator --show-sdk-path`/usr/include/mach-o/loader.h
Build for iPhone device
To build a static library for the iPhone SDK, you would change this:
export SDK=iphoneos
in the build.sh script above.
If you tried to use this library in the simulator, it would fail with the message building for iOS Simulator, but linking in object file built for iOS, file 'libuplink.a' for architecture arm64.
The output of otool -l would read:
...
Load command 1
cmd LC_BUILD_VERSION
cmdsize 24
platform 2
minos 15.0
sdk 16.2
ntools 0
...
Note: Platform 2 stands for PLATFORM_IOS and not for the simulator.
This will of course run perfectly on the device.
Related
I have a makefile that builds some C files and if I run it on an M1 mac the resulting library has the architecture arm64 which I thought it what is necessary for them to compile with an Xcode project for iOS. I discovered I can run the command otool -l libf2c.a | grep platform which should tell me what it was compiled for and in my case it returns platform1 which indicates macOS. Based on this, I think I need a value of platform2 for iOS.
The reason this is an issue is because in Xcode I get the error ld: building for iOS, but linking in object file built for macOS, file '/Users/e.../close.o' for architecture arm64.
Based on what I have been researching it seems iOS and macOS have the same architecture (arm64) but are a different 'platform'? But, I am not sure how the platform is determined. Is there some setting in my makefile I need to specify the platform? I am assuming that if I am able to get the platform to be iOS then Xcode will cooperate and be able to build the library I have generated.
The preferred way to compile for iOS via command line would probably be to use the xcrun command. This will allow you to specify the correct SDK for the platform you actually want to run on. For example:
prompt$ xcrun --sdk iphoneos --toolchain iphoneos clang -c test.c -o test.o -arch arm64
prompt$ otool -v -l test.o | grep platform
platform IOS
TL;DR: change your compiler invocation from plain clang to xcrun --sdk iphoneos --toolchain iphoneos clang.
My static lib is built with xcodebuild and then a fat lib is created from the simulator and device build result.
Here is my xcodebuild command:
xcodebuild OTHER_CFLAGS="-fembed-bitcode" -configuration "iphoneos" -target "${LIB_NAME}Common" -sdk iphoneos
xcodebuild OTHER_CFLAGS="-fembed-bitcode" -configuration "iphonesimulator" -target "${LIB_NAME}Common" -sdk iphonesimulator
lipo command:
lipo -create "${DEVICE_DIR}/lib${LIB_NAME}Common.a" "${SIMULATOR_DIR}/lib${LIB_NAME}Common.a" -output "${INSTALL_DIR}/include/${LIB_NAME}/lib${LIB_NAME}Common.a"
After checking the architectures in the fat lib, I got:
$ lipo -info MyLibCommon.a
Architectures in the fat file: MyLibCommon.a are: armv7 i386 x86_64 arm64
However, when I add the lib to a project via cocoapods and run the project on Apple's new Silicon (with arm64 chipset) on simulator, I got the following compile error:
building for iOS Simulator, but linking in object file built for iOS, file 'MyLibCommon.a' for architecture arm64
Excluding the arm64 architecture for Simulator is not an option because on Apple Silicon Mac has arm64 chipset.
Any idea how can I build a static library for Apple Silicon Simulator?
This is not possible.
In all likelihood, your simulator binaries are just i386 and x86_64. If you truly had arm64 iOS binaries and arm64 macOS binaries, lipo would error out on you:
fatal error: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/lipo: test.a.ios and test.a.macos have the same architectures (arm64) and can't be in the same fat output file
This happens no matter whether you try it with full-fledged binaries, unlinked object files or static libraries. The reason is simply a shortcoming in the fat file format: you can only have one slice per architecture. You want both arm64 iOS and the Apple Silicon simulator, but that would be 2x arm64.
You might be tempted to try and build a single thin arm64 binary that works on both iOS and macOS, but that too is impossible. Binaries are platform-locked:
% otool -l test.o.ios | fgrep -B1 -A5 LC_BUILD_VERSION
Load command 1
cmd LC_BUILD_VERSION
cmdsize 24
platform 2
sdk 14.2
minos 14.2
ntools 0
% otool -l test.o.macos | fgrep -B1 -A5 LC_BUILD_VERSION
Load command 1
cmd LC_BUILD_VERSION
cmdsize 24
platform 1
sdk 11.0
minos 11.0
ntools 0
Notice the platform 2 vs platform 1. The kernel will actually ignore this load command, but dyld will not. And you can't have two such load commands in a single binary either, that's considered invalid as well.
You might also remember talk of a "Universal 2" file format from Apple's announcement or something citing that - but they lied. There is no "universal 2", it's exactly the same file format it was a decade ago. When they say "universal 2", all they mean is "adding an arm64 slice to your macOS binary".
The way I see it, you have three options:
You build separate libraries and keep the names separate.
You never build both architectures at the same time.
You build the simulator target for x86_64 and run it under Rosetta.
The latter two are both being suggested widely across the internet, with number 2 as "build only for active architecture" and number 3 being "exclude arm64". Given that Rosetta is expected to go away eventually, the third option seems not viable for the long term.
I have a custom framework that I am archiving for use in another project. After updating to Xcode11 I get the following error in my project utilizing the framework.
Could not find module 'MyCustomFramework' for target 'x86_64-apple-ios-simulator'; found: arm64, armv7-apple-ios, arm64-apple-ios, arm, armv7
My framework works fine on Xcode10 but does not on 11. My target is set to NO for build active architectures only. I have the following for Valid Architectures; arm64e, armv7s, armv7, arm64.
My build script
exec > ${PROJECT_DIR}/${PROJECT_NAME}_archive.log 2>&1
UNIVERSAL_OUTPUTFOLDER=${BUILD_DIR}/${CONFIGURATION}-universal
if [ "true" == ${ALREADYINVOKED:-false} ]
then
echo "RECURSION: Detected, stopping"
else
export ALREADYINVOKED="true"
# make sure the output directory exists
mkdir -p "${UNIVERSAL_OUTPUTFOLDER}"
echo "Building for iPhoneSimulator"
xcodebuild -workspace "${WORKSPACE_PATH}" -scheme "${TARGET_NAME}" -configuration ${CONFIGURATION} -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 11 Pro Max' ONLY_ACTIVE_ARCH=NO ARCHS='i386 x86_64' BUILD_DIR="${BUILD_DIR}" BUILD_ROOT="${BUILD_ROOT}" ENABLE_BITCODE=YES OTHER_CFLAGS="-fembed-bitcode" BITCODE_GENERATION_MODE=bitcode clean build
# Step 1. Copy the framework structure (from iphoneos build) to the universal folder
echo "Copying to output folder"
cp -R "${ARCHIVE_PRODUCTS_PATH}${INSTALL_PATH}" "${UNIVERSAL_OUTPUTFOLDER}/"
# Step 2. Copy Swift modules from iphonesimulator build (if it exists) to the copied framework directory
SIMULATOR_SWIFT_MODULES_DIR="${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/${TARGET_NAME}.framework/Modules/${TARGET_NAME}.swiftmodule/."
if [ -d "${SIMULATOR_SWIFT_MODULES_DIR}" ]; then
cp -R "${SIMULATOR_SWIFT_MODULES_DIR}" "${UNIVERSAL_OUTPUTFOLDER}/${TARGET_NAME}.framework/Modules/${TARGET_NAME}.swiftmodule"
fi
# Step 3. Create universal binary file using lipo and place the combined executable in the copied framework directory
echo "Combining executables"
lipo -create -output "${UNIVERSAL_OUTPUTFOLDER}/${EXECUTABLE_PATH}" "${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/${EXECUTABLE_PATH}" "${ARCHIVE_PRODUCTS_PATH}${INSTALL_PATH}/${EXECUTABLE_PATH}"
# Step 4. Create universal binaries for embedded frameworks
#for SUB_FRAMEWORK in $( ls "${UNIVERSAL_OUTPUTFOLDER}/${TARGET_NAME}.framework/Frameworks" ); do
#BINARY_NAME="${SUB_FRAMEWORK%.*}"
#lipo -create -output "${UNIVERSAL_OUTPUTFOLDER}/${TARGET_NAME}.framework/Frameworks/${SUB_FRAMEWORK}/${BINARY_NAME}" "${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/${SUB_FRAMEWORK}/${BINARY_NAME}" "${ARCHIVE_PRODUCTS_PATH}${INSTALL_PATH}/${TARGET_NAME}.framework/Frameworks/${SUB_FRAMEWORK}/${BINARY_NAME}"
#done
# Step 5. Convenience step to copy the framework to the project's directory
ARCHIVE_PATH="${PROJECT_DIR}/Products"
rm -rf "${ARCHIVE_PATH}"
mkdir "${ARCHIVE_PATH}"
echo "Copying to project dir"
yes | cp -Rf "${UNIVERSAL_OUTPUTFOLDER}/${FULL_PRODUCT_NAME}" "${ARCHIVE_PATH}"
open "${ARCHIVE_PATH}"
fi
This line is incorrect
cp -R "${SIMULATOR_SWIFT_MODULES_DIR}" "${UNIVERSAL_OUTPUTFOLDER}/${TARGET_NAME}.framework/Modules/${TARGET_NAME}.swiftmodule"
Should be
cp -R "${SIMULATOR_SWIFT_MODULES_DIR}" "${UNIVERSAL_OUTPUTFOLDER}/${TARGET_NAME}.framework/Modules/"
You'll need to check in the target folder
"${UNIVERSAL_OUTPUTFOLDER}/${TARGET_NAME}.framework/Modules/"
If there are files like
x86_64-apple-ios-simulator.swiftdoc
x86_64-apple-ios-simulator.swiftmodule
x86_64.swiftdoc
x86_64.swiftmodule
Before Xcode 11, Apple seems neglect those files so your script works well.
From Xcode 11 those files are seem to be checked rigorously.
Swift 5.0-5.1, Xcode 11 Open Xcode, <your project>, Build Settings, Build Active Architecture Only and change to <NO> for Debug and Release. Architectures set/leave in Standard Architecture -$(ARCHS_STANDART), important is next step:Valid Architecture: armv7, armv7s, amr64, amr64e, ADD here x86_64 and if you need add i386 for Debug and Release. (String: armv7, armv7s, amr64, amr64e, x86_64)
Choose any simulator in your simulator list and BUILT IT. DONE.
I hope it is works for you.
Description of Architecture:
armv64: iPhoneX, iPhone 5s-8, iPad Air — iPad Pro
armv7 : iPhone3Gs-5c, iPad WIFI(4th gen)
armv6 : iPhone — iPhone3G
-the above if for real devices
i386 : 32-bit simulator
x86_64 : 64-bit simulator
I am trying to compile a C library to use it in my iOS project, and I want to embed bitcode.
I can successfully build static libraries targeting each arch. And those static library do contain bitcode (checked using otool), but the dynamic library doesn't contain bitcode. Why? Is bitcode not supported in dylib?
The library I am trying to build is xz. Here is the script
build_iOS()
{
ARCH=$1
if [ $ARCH == "i386" ] || [ $ARCH == "x86_64" ];
then
SDKROOT="$(xcodebuild -version -sdk iphonesimulator | grep -E '^Path' | sed 's/Path: //')"
else
SDKROOT="$(xcodebuild -version -sdk iphoneos | grep -E '^Path' | sed 's/Path: //')"
fi
export CC="$(xcrun -sdk iphoneos -find clang)"
export CFLAGS="-fembed-bitcode -isysroot $SDKROOT -arch ${ARCH} -miphoneos-version-min=9.0"
export LDFLAGS="-arch ${ARCH} -isysroot $SDKROOT"
if [ $ARCH == "i386" ] || [ $ARCH == "x86_64" ];
then
./configure --prefix=$XZPATH/build/iOS/$ARCH --host=i686-apple-darwin11 --disable-static --enable-shared
else
./configure --prefix=$XZPATH/build/iOS/$ARCH --host=arm-apple-darwin --disable-static --enable-shared
fi
make && make install && make clean
}
build_iOS i386
build_iOS x86_64
build_iOS armv7
build_iOS armv7s
build_iOS arm64
Thanks!
It looks like I cannot add bitcode to dylibs. I tried building several dylibs, then use otool -l path_to_dylib | grep bitcode to test if they contain any bitcode, all got nothing.
More evidence:
in Xcode(7.3.1), macOS (previously called OS X) targets don't have enable bitcode option in build settings
in the bitcode section of App Thinning, Apple didn't mentioned bitcode on macOS. Plus, App Thinning is only available on iOS, watchOS and tvOS.
I currently don't know why macOS apps don't have enable bitcode option in build setting. Maybe it's because Mac App store is not the only way for distributing mac apps? And people might copy one mac app from one mac app to another using USB sticks?
I was not able to verify bitcode in my bitcode enabled dynamic library via cmd line tools (otool, file or clang). Also comparing the diff between bitcode and non-bitcode build showed no difference, except the filesize.
Interestingly when using the dynamic library withouth bitcode enabled in an actual app and archiving xcode will fail to archive when I use the non-bitcode build:
ld: bitcode bundle could not be generated because '...' was built without full bitcode. All frameworks and dylibs for bitcode must be generated from Xcode Archive or Install build for architecture armv7
When building the dylib with bitcode enabled the filesize increases a lot and also xcode does not fail on archiving a sample project with bitcode enabled. So I am pretty sure that bitcode must be included in the dynamic lib, although we haven't found a way to verify that via cmd line tools yet...
I'm building my own framework by following the guide on site
Create a framework for iOS - RayWenderlich
It works well, but when I integrate the framework on test project, it catches error as "undefined symbol for architecture arm64". When I check the supported architectures by the command
<myframework>.framework xcrun lipo -info <myframework>
it misses 2 architectures armv7s and x86_64
Here is my run script on Aggregate target
set -e
# If we're already inside this script then die
if [ -n "$RW_MULTIPLATFORM_BUILD_IN_PROGRESS" ]; then
exit 0
fi
export RW_MULTIPLATFORM_BUILD_IN_PROGRESS=1
RW_FRAMEWORK_NAME=${PROJECT_NAME}
RW_INPUT_STATIC_LIB="lib${PROJECT_NAME}.a"
RW_FRAMEWORK_LOCATION="${BUILT_PRODUCTS_DIR}/${RW_FRAMEWORK_NAME}.framework"
function build_static_library {
# Will rebuild the static library as specified
# build_static_library sdk
xcrun xcodebuild -project "${PROJECT_FILE_PATH}" \
-target "${TARGET_NAME}" \
-configuration "${CONFIGURATION}" \
-sdk "${1}" \
ONLY_ACTIVE_ARCH=NO \
BUILD_DIR="${BUILD_DIR}" \
OBJROOT="${OBJROOT}" \
BUILD_ROOT="${BUILD_ROOT}" \
SYMROOT="${SYMROOT}" $ACTION
}
function make_fat_library {
# Will smash 2 static libs together
# make_fat_library in1 in2 out
xcrun lipo -create "${1}" "${2}" -output "${3}"
}
# 1 - Extract the platform (iphoneos/iphonesimulator) from the SDK name
if [[ "$SDK_NAME" =~ ([A-Za-z]+) ]]; then
RW_SDK_PLATFORM=${BASH_REMATCH[1]}
else
echo "Could not find platform name from SDK_NAME: $SDK_NAME"
exit 1
fi
# 2 - Extract the version from the SDK
if [[ "$SDK_NAME" =~ ([0-9]+.*$) ]]; then
RW_SDK_VERSION=${BASH_REMATCH[1]}
else
echo "Could not find sdk version from SDK_NAME: $SDK_NAME"
exit 1
fi
# 3 - Determine the other platform
if [ "$RW_SDK_PLATFORM" == "iphoneos" ]; then
RW_OTHER_PLATFORM=iphonesimulator
else
RW_OTHER_PLATFORM=iphoneos
fi
# 4 - Find the build directory
if [[ "$BUILT_PRODUCTS_DIR" =~ (.*)$RW_SDK_PLATFORM$ ]]; then
RW_OTHER_BUILT_PRODUCTS_DIR="${BASH_REMATCH[1]}${RW_OTHER_PLATFORM}"
else
echo "Could not find other platform build directory."
exit 1
fi
# Build the other platform.
build_static_library "${RW_OTHER_PLATFORM}${RW_SDK_VERSION}"
# If we're currently building for iphonesimulator, then need to rebuild
# to ensure that we get both i386 and x86_64
if [ "$RW_SDK_PLATFORM" == "iphonesimulator" ]; then
build_static_library "${SDK_NAME}"
fi
# Join the 2 static libs into 1 and push into the .framework
make_fat_library "${BUILT_PRODUCTS_DIR}/${RW_INPUT_STATIC_LIB}" \
"${RW_OTHER_BUILT_PRODUCTS_DIR}/${RW_INPUT_STATIC_LIB}" \
"${RW_FRAMEWORK_LOCATION}/Versions/A/${RW_FRAMEWORK_NAME}"
# Ensure that the framework is present in both platform's build directories
cp -a "${RW_FRAMEWORK_LOCATION}/Versions/A/${RW_FRAMEWORK_NAME}" \
"${RW_OTHER_BUILT_PRODUCTS_DIR}/${RW_FRAMEWORK_NAME}.framework/Versions/A/${RW_FRAMEWORK_NAME}"
# Copy the framework to the user's desktop
ditto "${RW_FRAMEWORK_LOCATION}" "${HOME}/Desktop/${RW_FRAMEWORK_NAME}.framework"
I suppose the build error when integrating framework to test project causes by the missing architecture when building framework
Solved, in my case: in the main target (RWUIControls, if you refer to Ray W. post) I added manually the armv7s architecture.
See screenshot below:
if I run the command from the terminal:
xcrun lipo -info RWUIControls
inside the framework folder now I get:
Architectures in the fat file: RWUIControls are: armv7 armv7s i386 x86_64 arm64
It's easy only change the iOS Development Target in the project Info.
In my case I had selected iOS 13 then I changed it to iOS 10 and now I have those Archs -> armv7 i386 x86_64 arm64.
These are the Architectures depending on the iOS device.
armv64: iPhoneX, iPhone 5s-8, iPad Air — iPad Pro
armv7 : iPhone3Gs-5c, iPad WIFI(4th gen)
armv6 : iPhone — iPhone3G
the above if for real devices
i386 : 32-bit simulator
x86_64 : 64-bit simulator