Tuesday, October 11, 2011

Creating a Universal Framework from a 3rd Party Framework Project

One of the things I find irritating about development for iOS is when projects are included directly inside of another project or workspace.  Or even the case where they only provide a static library that you must then separately copy the headers into one of your project folders.  Framework packages seem much cleaner.  So I decided to modify the UISpec base project and create targets to output a universal framework that can be included in any other project.  One drawback of this approach though is that whenever the UISpec projects are updated we either have to figure out which files changed and update our own custom UISpec project or re-create the targets to create the universal framework.  In any case, here is how you create a universal framework from the UISpec project in Xcode 4.2 built for iOS 5.

Edit: The content below was modified to be more generic and calls out UISpec specific steps where necessary.

Cleanup Source Control Files (Optional)
--------------------------------------------------
- (optional) Remove all svn or git directories by running 'find . -name ".svn" -exec rm -rf {} \;' or 'find . -name ".git*" -exec rm -rf {} \;' from the base project directory.

Create New Bundle Target for Framework
---------------------------------------------------
- Open the framework project.
- (UISpec specific) Modify specs/main.m.  Remove "@implementation main" and "@end" from the file.
- Rename the static library target to "<Framework Name> Static Library".  ex. rename "UISpec" target to "UISpec Static Library".  This is to allow creation of a bundle target with the name of the framework so that the framework is built as <Framework Name>.framework.
- (UISpec specific) Go to the project level "Build Settings" and change the compiler to "LLVM GCC 4.2".
- Add a new target of type "Bundle" named <Framework Name>.  ex. "UISpec" This will be the framework target.
- Go to the "Build Settings" for the newly created target.
- Change the Base SDK to "iOS 5.0" or whichever version is appropriate for your framework.
- Change "Build Active Architecture Only" to "No".
- Change "Architectures" to "$(ARCHS_STANDARD_32_BIT)".
- Change "Valid Architectures" to "$(ARCHS_STANDARD_32_BIT)".
- (UISpec specific) Change the "Compiler for C/C++/Objective-C" to "LLVM GCC 4.2"
- Change "Dead Code Stripping" to "No".
- Change "Link With Standard Libraries" to "No".
- Change "Mach-O Type" to "Relocatable Object File".
- Change "Wrapper Extension" to "framework".
- Change "Generate Debugger Symbols" to "No".  (project may not always contain this option)
- Go to the "Build Phases" tab.
- Click "Add Phase" and then select "Add Copy Headers".
- In the "Copy Headers" section add the headers from the project as public/private/project headers as appropriate.  Click the "+" button and search for "*.h".  Select all(or appropriate subset because there may be example or other unnecessary files that don't need to be added) then click "Add".  Move the headers up to the appropriate section.
- In the "Compile Sources" section add the source files.  Click the "+" button and search for "*.m".  Select all(or appropriate subset because there may be some example files in the framework project that are not needed) then click "Add".
- Under the "Link Binary With Libraries" section and "Copy Bundle Resources" section, remove all entries.
- Add any necessary imports required by the framework classes to the <Framework Name>-Prefix.pch file.
- (UISpec specific)Under UISpec/SupportingFiles/UISpec-Prefix.pch remove the Cocoa.h entry and add the following entries:
    #import <Foundation/Foundation.h>
    #import <UIKit/UIKit.h>

Create Target for Universal Framework
-------------------------------------------------
- Create an "Aggregate" target named "<Framework Name> Universal Framework".
- Update the "Build Settings" by changing "SDKROOT" to "iphoneos".
- Click "Add Build Phase" and then "Add Run Script".  Copy the script below into the script area in "Run Script".  Be sure to update the "FMK_NAME" property to the name of your framework.

# Sets the target folders and the final framework product.
FMK_NAME=UISpec
FMK_VERSION=A

# Install dir will be the final output to the framework.
# The following line create it in the root folder of the current project.
INSTALL_DIR=${SRCROOT}/Products/${FMK_NAME}.framework

# Working dir will be deleted after the framework creation.
WRK_DIR=build
DEVICE_DIR=${WRK_DIR}/Release-iphoneos/${FMK_NAME}.framework
SIMULATOR_DIR=${WRK_DIR}/Release-iphonesimulator/${FMK_NAME}.framework

# Building both architectures.
xcodebuild -configuration "Release" -target "${FMK_NAME}" -sdk iphoneos
xcodebuild -configuration "Release" -target "${FMK_NAME}" -sdk iphonesimulator

# Cleaning the oldest.
if [ -d "${INSTALL_DIR}" ]
then
rm -rf "${INSTALL_DIR}"
fi

# Creates and renews the final product folder.
mkdir -p "${INSTALL_DIR}"
mkdir -p "${INSTALL_DIR}/Versions"
mkdir -p "${INSTALL_DIR}/Versions/${FMK_VERSION}"
mkdir -p "${INSTALL_DIR}/Versions/${FMK_VERSION}/Resources"
mkdir -p "${INSTALL_DIR}/Versions/${FMK_VERSION}/Headers"

# Creates the internal links.
# It MUST uses relative path, otherwise will not work when the folder is copied/moved.
ln -s "${FMK_VERSION}" "${INSTALL_DIR}/Versions/Current"
ln -s "Versions/Current/Headers" "${INSTALL_DIR}/Headers"
ln -s "Versions/Current/Resources" "${INSTALL_DIR}/Resources"
ln -s "Versions/Current/${FMK_NAME}" "${INSTALL_DIR}/${FMK_NAME}"

# Copies the headers and resources files to the final product folder.
cp -R "${DEVICE_DIR}/Headers/" "${INSTALL_DIR}/Versions/${FMK_VERSION}/Headers/"
cp -R "${DEVICE_DIR}/" "${INSTALL_DIR}/Versions/${FMK_VERSION}/Resources/"

# Removes the binary and header from the resources folder.
rm -r "${INSTALL_DIR}/Versions/${FMK_VERSION}/Resources/Headers" "${INSTALL_DIR}/Versions/${FMK_VERSION}/Resources/${FMK_NAME}"

# Uses the Lipo Tool to merge both binary files (i386 + armv6/armv7) into one Universal final product.
lipo -create "${DEVICE_DIR}/${FMK_NAME}" "${SIMULATOR_DIR}/${FMK_NAME}" -output "${INSTALL_DIR}/Versions/${FMK_VERSION}/${FMK_NAME}"

rm -r "${WRK_DIR}"

Xcode Scheme cleanup
-----------------------------
Since you had renamed the <Framework Name> target and created a new one, you'll probably see <Framework Name> and <Framework Name> 2 as schemes in your project.  Manage your schemes and delete those two schemes.  Then click the "Autocreate" button.  The schemes should now have the updated names.

That's it!  You should be able to build the framework using the new "<Framework Name> Universal Framework" target.  The framework output should be in the base project folder under a directory named "Products".

No comments:

Post a Comment