How to support both armv6 and armv7s for release build in xcode 4.5

Solution 1:

I've been able to do this successfully with my app that's in the App Store. It supports armv6, armv7, and armv7s and iOS versions from 4.2 to 6.0. I've verified that it runs on older devices (iPhone 3G, iPod touch 2g) all the way through the iPhone 5.

This method requires having both Xcode 4.5 and an older version of Xcode installed simultaneously. I'm still on 4.3.2 for my older version, but 4.4 should work as well.

I had screenshots for this answer, but Stack Overflow won't let me post them because I'm new. :(

Setup in Xcode 4.5

  1. Add a new build configuration for your armv6 build. I duplicated the Release config and named it Release_armv6.

  2. Set the Architectures and Valid Architectures for your build configurations. For all but Release_armv6, use the default. For Release_armv6, manually set it to armv6. http://i.stack.imgur.com/h8Mpl.png

  3. If you're using iOS 6 features that Xcode 4.4 and below won't understand, you'll need to #ifdef these out for your armv6 build. In Build Settings under Other C Flags and Other C++ Flags I added -DARMV6_ONLY to my Release_armv6 config. Then wherever the code uses a new iOS 6 API, I do something like #ifndef ARMV6_ONLY / #endif as appropriate. http://i.stack.imgur.com/czF6J.png

  4. Add a new scheme and set it to use the Release_armv6 build configuration in all cases.

  5. Under Build Phases, add a Run Script Build Phase with the following script (set the Shell to /bin/csh). This is where the magic happens. Edit the Configuration section: Determine your full path to the Release_armv6 build and substitute it for ARMV6_EXECUTABLE_PATH. Also set MINIMUM_OS.



    #
    # Script to add armv6 architecture to iOS executable built with Xcode 4.5
    #

    #################
    # Configuration #
    #################
    # Change this to the full path where Xcode 4.4 (or below) puts your armv6 output
    setenv ARMV6_EXECUTABLE_PATH "$BUILD_ROOT/Release_armv6-iphoneos/$EXECUTABLE_PATH"

    # Your "real" minimum OS version since Xcode 4.5 wants to make it iOS 4.3
    # Must be 4.2 or below if you are supporting armv6...
    setenv MINIMUM_OS 4.2
    #####################
    # End configuration #
    #####################


    # For debugging
    echo CURRENT_ARCH = $CURRENT_ARCH
    echo CONFIGURATION = $CONFIGURATION

    # Don't need to do this for armv6 (built in older Xcode), simulator (i386), or debug build
    if ("$CURRENT_ARCH" == "armv6") exit 0
    if ("$CURRENT_ARCH" == "i386") exit 0
    if ("$CONFIGURATION" != "Release" && "$CONFIGURATION" != "Beta Test") exit 0

    # Paths
    setenv LIPO_PATH "$CODESIGNING_FOLDER_PATH/${EXECUTABLE_NAME}.lipo"
    setenv FINAL_PATH "$CODESIGNING_FOLDER_PATH/$EXECUTABLE_NAME"
    setenv FULL_INFO_PLIST_PATH "$CONFIGURATION_BUILD_DIR/$INFOPLIST_PATH"

    # Debug / sanity check
    lipo -info "$FINAL_PATH"
    ls -l "$ARMV6_EXECUTABLE_PATH"

    # Make sure something exists at $LIPO_PATH even if the next command fails
    cp -pv "$FINAL_PATH" "$LIPO_PATH"

    # If rebuilding without cleaning first, old armv6 might already be there so remove it
    # If not, lipo won't output anything (thus the cp command just above)
    lipo -remove armv6 -output "$LIPO_PATH" "$FINAL_PATH"

    # Add armv6 to the fat binary, show that it worked for debugging, then remove temp file
    lipo -create -output "$FINAL_PATH" "$ARMV6_EXECUTABLE_PATH" "$LIPO_PATH"
    lipo -info "$FINAL_PATH"
    rm -f "$LIPO_PATH"

    # Change Info.plist to set minimum OS version to 4.2 (instead of 4.3 which Xcode 4.5 wants)
    /usr/libexec/PlistBuddy -c "Set :MinimumOSVersion $MINIMUM_OS" "$FULL_INFO_PLIST_PATH"
    plutil -convert binary1 "$FULL_INFO_PLIST_PATH"

Build Process

When you're ready to create a release build, do it in the following order:

  1. Close Xcode 4.5 and open Xcode 4.4 or below. Select your armv6 scheme and build it.

  2. Close Xcode 4.4 or below and open Xcode 4.5. Select your Release scheme and build it.

That's pretty much it. Check the build output to verify that you got what you want - an executable with three architectures in it. The last output from the run script should tell you this.

If anyone has ideas to improve this, please feel free. I imagine you might be able to get fancy and call Xcode 4.4's "xcodebuild" command from within the build script, alleviating the need to switch between Xcode versions at all. But this works well enough for me. ;)

Caveats:

  • Just to be safe, you might want to edit your xib files in the older version of Xcode. So far it seems like 4.5 is backwards compatible, but you never know.

  • In fact, you might consider just doing most of your development, except for iOS 6-specific stuff, in the older Xcode. Depends on whatever's easiest for you.

Solution 2:

There is a another way as gcc-4.2 still supports armv6, which won't require you to close Xcode 4.5 an open a previous version (for compilation, but not for running app on a 4.2 device) :

  • Add armv6 to both valid archs and archs :

Archs : $(ARCHS_STANDARD_32_BIT) armv6

Valid Architectures : armv6 armv7 armv7s

  • Vim (or TextEdit) your project.pbxproj file to replace IPHONEOS_DEPLOYMENT_TARGET to 4.0 - 4.1 - 4.2 as you need, Xcode 4.5 won't let you get below 4.3.

Then, if you build your project, you will see warnings :

warning: no rule to process file '$(PROJECT_DIR)/App/AppDelegate.m' of type sourcecode.c.objc for architecture armv6
warning: no rule to process file '$(PROJECT_DIR)/App/SomeFile.c' of type sourcecode.c.c for architecture armv6
  • Add a Build Rule for source files with names matching : *.[mc] that will use LLVM GCC 4.2

It works for static libraries, but not for apps :

ld: file is universal (4 slices) but does not contain a(n) armv6 slice: /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS6.0.sdk/usr/lib/crt1.3.1.o for architecture armv6
  • For making it works for apps, we need to add the armv6 slice to this object file (which comes with the 5.1 SDK) :
lipo /path/to-4.4/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS5.1.sdk/usr/lib/crt1.3.1.o -extract armv6 -output /tmp/crt1.3.1-armv6.o
lipo /Applications/Xcode.app/Contents//Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS6.0.sdk/usr/lib/crt1.3.1.o /tmp/crt1.3.1-armv6.o -create -output /tmp/crt1.3.1-armv677s.o
mv /Applications/Xcode.app/Contents//Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS6.0.sdk/usr/lib/crt1.3.1.o /Applications/Xcode.app/Contents//Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS6.0.sdk/usr/lib/crt1.3.1.o.bkp
mv /tmp/crt1.3.1-armv677s.o /Applications/Xcode.app/Contents//Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS6.0.sdk/usr/lib/crt1.3.1.o

Compile your project and check that your app contains all archs :

$ file DerivedData/TestApp/Build/Products/Debug-iphoneos/TestApp.app/TestApp 
DerivedData/TestApp/Build/Products/Debug-iphoneos/TestApp.app/TestApp: Mach-O universal binary with 3 architectures
DerivedData/TestApp/Build/Products/Debug-iphoneos/TestApp.app/TestApp (for architecture armv6): Mach-O executable arm
DerivedData/TestApp/Build/Products/Debug-iphoneos/TestApp.app/TestApp (for architecture armv7): Mach-O executable arm
DerivedData/TestApp/Build/Products/Debug-iphoneos/TestApp.app/TestApp (for architecture cputype (12) cpusubtype (11)):  Mach-O executable arm

Note that the dSYM file also contains all archs (useful for crash report symbolification) :

$ file DerivedData/TestApp/Build/Products/Debug-iphoneos/TestApp.app.dSYM/Contents/Resources/DWARF/TestApp 
DerivedData/TestApp/Build/Products/Debug-iphoneos/TestApp.app.dSYM/Contents/Resources/DWARF/TestApp: Mach-O universal binary with 3 architectures
DerivedData/TestApp/Build/Products/Debug-iphoneos/TestApp.app.dSYM/Contents/Resources/DWARF/TestApp (for architecture armv6):   Mach-O dSYM companion file arm
DerivedData/TestApp/Build/Products/Debug-iphoneos/TestApp.app.dSYM/Contents/Resources/DWARF/TestApp (for architecture armv7):   Mach-O dSYM companion file arm
DerivedData/TestApp/Build/Products/Debug-iphoneos/TestApp.app.dSYM/Contents/Resources/DWARF/TestApp (for architecture cputype (12) cpusubtype (11)):    Mach-O dSYM companion file arm

I've sucessfully installed and launched the app on an iOS 4.2 2gen iPod touch by opening xcode 4.4.1, then Product -> Run without building.

  • When you Archive your product, you may experience again the Apple Mach-O Linker error, this time involving other files, such as libarclite_iphoneos.a or libclang_rt.ios.a:
ld: file is universal (2 slices) but does not contain a(n) armv6 slice: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/arc/libarclite_iphoneos.a for architecture armv6
ld: file is universal (2 slices) but does not contain a(n) armv6 slice: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/clang/4.1/libclang_rt.ios.a for architecture armv6

The procedure used for crt1.3.1.o applies to these files too, and will fix the error allowing Xcode to successfully archive your project: you can use the path printed by ld to find the file and join the armv6 slice with lipo; just keep in mind that libclang_rt.ios.a in the previous versions of Xcode isn't located in Xcode.app/[...]/usr/lib/clang/4.1 but in Xcode.app/[...]/usr/lib/clang/4.0.

I've successfully archived the file, deployed it with an ad-hoc distribution profile, and tested on iPhone 3G (4.2.1) and iPhone 3GS (6.0).

  • Last issue : we can't launch app. In the Organizer, there is the message : Devices of type “iPhone 3G” are not supported by this version of Xcode.

But an ls in the DeviceSupport shows :

 ls /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/DeviceSupport/ 
4.2          4.3          5.0          5.1          6.0 (10A403)

With no diffs in the 4.2 directory from Xcode 4.4.1.

The question is now : how Xcode detect is device is supported or not ?

Opening /Applications/Xcode.app/Contents/Developer//Platforms/iPhoneOS.platform/Developer//Library/PrivateFrameworks/DTDeviceKitBase.framework/DTDeviceKitBase with Hex Fiend (or another hex editor), and replacing ascii 4.3 with 4.2 make the error message disappear, and app installed on the device are listed (but device bullet in the device list is still red).

Then we need to edit /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/Library/PrivateFrameworks//DTDeviceKit.framework/Versions/Current/DTDeviceKit and replace :

Expired.deviceArchitecture.iPhone1,1.iPhone1,2.iPod1,1.iPod2,1.iPod2,2.armv6

to :

Expired.deviceArchitecture.iPhone0,1.iPhone0,2.iPod0,1.iPod0,1.iPod0,2.armv5

Then we have an orange bullet in the Organizer (Xcode 4.5.1) :

The version of iOS on “iPhone” is too old for use with this version of the iOS SDK. Please restore the device to a version of the OS listed below.

OS Installed on iPhone
4.2.1 (8C148)

Xcode Supported iOS Versions
6.0 (10A403)
5.1
5.0
4.3

The question is now : where Xcode Supported iOS Versions are defined ?

As there is a 4.2 directory in /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/DeviceSupport/, it should already be supported...

Tried to copy iPhoneOS4.2.sdk from Xcode 4.4.1 to /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/, but it don't make device supported.

So haven't found how to add 4.2 device support in Xcode 4.5. Any ideas ?

Conclusion : compiling for armv6/7/7s within Xcode 4.5 is possible. But it is not possible to start an app on a 4.2 armv6 device without starting Xcode 4.4.

Big update : it works with Xcode 4.5.2 !

Now the bullet is green in Xcode 4.5.2 :-) The device appear in the drop down list near the Run button. But when trying to run the app, got the message :

Xcode cannot run using the selected device.
Choose a destination with a supported architecture in order to run on this device.

Simply add armv6 to the valid architectures :-)

Other note : the Build Rule for source files with names matching : *.[mc] can use LLVM GCC 4.2 or Apple LLVM compiler 4.1, or Default compiler

Solution 3:

Thanks for this useful script !

I successfully combined all infos from this entire post, the resulting complete script is below. This script requires having both Xcode 4.5.x and a previous Xcode version supporting armv6 (Xcode 4.4.1 for instance, installed in /Applications/Xcode 4.4.1.app)

The script does NOT require to compile first in xcode 4.4.x, you just have to launch your latest Xcode, select the Release configuration and build. (the Release-armv6 configuration should have been defined like mentioned in the original post from Mike).

It will produce a .app compatible with armv6 armv7 and armv7s

Thanks to Mike for the original script !

#################
# Configuration #
#################
# Change this to the full path where Xcode 4.4 (or below) puts your armv6 output
setenv ARMV6_OUTPUT_PATH     "$BUILD_ROOT/Release-armv6-iphoneos/"
setenv ARMV6_EXECUTABLE_PATH "$ARMV6_OUTPUT_PATH$EXECUTABLE_PATH"

# Your "real" minimum OS version since Xcode 4.5 wants to make it iOS 4.3
# Must be 4.2 or below if you are supporting armv6...
setenv MINIMUM_OS 4.2
#####################
# End configuration #
#####################

# For debugging
echo CURRENT_ARCH = $CURRENT_ARCH
echo CONFIGURATION = $CONFIGURATION

# Don't need to do this for armv6 (built in older Xcode), simulator (i386), or debug build
#if ("$CURRENT_ARCH" == "armv6") exit 0
if ("$CURRENT_ARCH" == "i386") exit 0
if ("$CONFIGURATION" != "Release" && "$CONFIGURATION" != "Beta Test") exit 0

# Paths
setenv LIPO_PATH "$CODESIGNING_FOLDER_PATH/${EXECUTABLE_NAME}.lipo"
setenv FINAL_PATH "$CODESIGNING_FOLDER_PATH/$EXECUTABLE_NAME"
setenv FULL_INFO_PLIST_PATH "$CONFIGURATION_BUILD_DIR/$INFOPLIST_PATH"

#log file for armv6 build
echo "------------------------- BUILDING ARMV6 NOW -------------------------"
setenv LOGFILE "$BUILD_ROOT/buildarmv6.txt"
setenv CONFIGURATION_ARMV6 "${CONFIGURATION}-armv6"
#build armv6 version
echo "Building $FULL_PRODUCT_NAME armv6         CONFIG=$CONFIGURATION-armv6            target=$TARGETNAME"
"/Applications/Xcode 4.4.1.app/Contents/Developer/usr/bin/xcodebuild" -project         "${PROJECT_FILE_PATH}" -target "${TARGETNAME}" -sdk "${PLATFORM_NAME}" -configuration "$CONFIGURATION-armv6" CONFIGURATION_BUILD_DIR="$ARMV6_OUTPUT_PATH" >> "$LOGFILE"
echo "---------------------------- ARMV6 BUILT  -------------------------"
# to check for armv6 build errors
open "$LOGFILE"

# Debug / sanity check
lipo -info "$FINAL_PATH"
ls -l "$ARMV6_EXECUTABLE_PATH"

# Make sure something exists at $LIPO_PATH even if the next command fails
cp -pv "$FINAL_PATH" "$LIPO_PATH"

# If rebuilding without cleaning first, old armv6 might already be there so remove it
# If not, lipo won't output anything (thus the cp command just above)
lipo -remove armv6 -output "$LIPO_PATH" "$FINAL_PATH"

# Add armv6 to the fat binary, show that it worked for debugging, then remove temp file
lipo -create -output "$FINAL_PATH" "$ARMV6_EXECUTABLE_PATH" "$LIPO_PATH"
echo "------------------------- CHECK ARMV6 ARMV7 ARMV7S ARE MENTIONED BELOW -------------------------"
lipo -info "$FINAL_PATH"
echo "------------------------------------------------------------------------------------------------"
rm -f "$LIPO_PATH"

# Change Info.plist to set minimum OS version to 4.2 (instead of 4.3 which Xcode 4.5 wants)
/usr/libexec/PlistBuddy -c "Set :MinimumOSVersion $MINIMUM_OS" "$FULL_INFO_PLIST_PATH"
plutil -convert binary1 "$FULL_INFO_PLIST_PATH"

Solution 4:

Thanks for the post. We have a few apps to build so we automated the armv6 build using xcodebuild as you suggested. This is the part of our script (modified as we use bash) that does that, which can be added to your script above. This could be added before "# Debug / sanity check"

setenv LOGFILE "/Users/xyz/Desktop/buildarmv6.txt"

setenv CONFIGURATION_ARMV6 "${CONFIGURATION}_armv6"
echo "Building $FULL_PRODUCT_NAME armv6         CONFIG=$CONFIGURATION_ARMV6         target=$TARGETNAME"

"/Applications/Xcode 4.4.1.app/Contents/Developer/usr/bin/xcodebuild" -project "${PROJECT_FILE_PATH}" -target "${TARGETNAME}" -sdk "${PLATFORM_NAME}" -configuration "$CONFIGURATION_ARMV6" >> "$LOGFILE"

echo "Built armv6"
open "$LOGFILE" # to check for armv6 build errors

Solution 5:

Thank to Mike for this useful tutorial and script. As mentioned by Piotr in comments, the script is failing if you run the archive command from Xcode since it use another build directory for archiving.

Here below is my modification to the script to enable it for both normal release build and archive specific build.

It assumes that the armv6 build is run before as per original instructions from Mike. It use bash syntax because it is easier for me to strip-out the common base build directory. So this implies translation of the original script to bash which is only a matter of replacing setenv by export and changing the if statements syntax.

# Find the common base directory for both build
XCODE_BUILD=${BUILD_ROOT%%/Build*}
# Change this to the full path where Xcode 4.4 (or below) puts your armv6 output, using the previously derived base
export ARMV6_EXECUTABLE_PATH="$XCODE_BUILD/Build/Products/Release_armv6-iphoneos/$EXECUTABLE_PATH"