Вы находитесь на странице: 1из 33

Creating

a Versioned, Documented Framework for iOS


using Xcode 4, GIT and DoxyGen


V1.23

Till Toenshoff, 2011

Author .......................................................................................................................... 3 Motivation .................................................................................................................... 3 Results ......................................................................................................................... 3 The Release Folder .................................................................................................. 3 The Framework ........................................................................................................ 3 The Version Information ........................................................................................... 4 The Documentation .................................................................................................. 5 Status ....................................................................................................................... 6 Creating the Xcode project .......................................................................................... 7 Framework and Versioning Run Scripts ...................................................................... 9 Building .................................................................................................................... 9 Additional Files .......................................................................................................... 17 Re/Sources............................................................................................................. 17 Making use of that Version information...................................................................... 23 Main definition file preprocessor Define .............................................................. 23 Main implementation file constant String............................................................. 23 The Build Process ...................................................................................................... 24 Debugging .............................................................................................................. 24 Distribution ............................................................................................................. 24 Rendering a Documentation ...................................................................................... 25 Building .................................................................................................................. 25 DoxyGen .................................................................................................................... 30 Create a DoxyGen config file using the DoxyGen GUI frontend Wizard ................ 30

Author
Till Toenshoff ttoenshoff@cellular.de

Motivation
Creating highly polished, complete and useful binary modules for reuse within Xcode projects. Plain static libraries would not be sufficient as those are not produced for both, simulator and device compatibility. Creating both flavors manually and joining them using lipo is feasible but takes many manual steps. As that process can be automated, why not going the extra mile and creating a proper framework together with a real docset, usable from within Xcode. And now, that we intend to automate the entire process, why not including an automated versioning scheme allowing us to identify the exact version of the resulting framework.

Results
The Release Folder
You will receive a folder consisting of the Framework and the DocSet.

The Framework
The resulting Framework pretty much looks, feels and works like one of the Applesupplied Frameworks. But unlike Apples Frameworks, this one contains a static, universal library.

The Version Information


The Framework will be versioned according to your most recent GIT-Tag, GIT-Patch and the Build-Number. For example, lets assume you tagged your latest version within GIT to 1.2 and you did two commits since that tagging happened. Now let us suppose you have built that project 123 times. As a result, your BundleVersion (Info.plist) will be set towards 1.2.2.123. Additionally, your Info.plist will contain a CFBuildHash value which will be set towards the latest GIT-Commit-Hash. That way, you will always be able to identify the correct version of your sources being used for a certain release. Within the Framework, you will, additionally, have a FRAMEWORK_VERSION preprocessor define telling you the current version and a string constant that should be named something like k$(PROJECT_NAME)Version e.g. kCellularDebuggingVersion. Last but not least, even your documentation will be containing this exact versionidentifier on the front-page.

The Documentation
The DoxyGen generated DocSet is usable just like any other DocSet supplied with Xcode. For more information regarding DoxyGen and its syntax for automated documentation generation, see one of those DoxyGen manual pages. Navigate to Xcode Help->Documentation and API Reference.

Now click on that Eye-Button and select your freshly created DocSet.

Alt-Click on any documented symbol within any opened source-file and your new documentation will be used right within the Xcode quick-reference feature.


The linked symbol will lead you right into the full context and description of that symbol.

Documentation showing the Release-Tag, the Patch-Level, the Build-Counter (and the GIT-Commit-Hash)

Status
The current solution is, in parts, a bit cumbersome and has its caveats the biggest being a lot of initial preparation. I feel that this initial work is totally worth it, especially considering that it is a one-time-investment.

Creating the Xcode project

Create a new Xcode project

Select a Cocoa Touch Static Library

Enter a Product Name

Framework and Versioning Run Scripts


Building

Select the project root item and switch to the project main target

Add a new Build Phase Run Script

Expand the Run Script item

Enter the following script:


# NOTE: the project has to include a Version.plist for storing some meta-info # # First, we get the GIT-related information into our Version.plist # echo "Gathering GIT information" buildTag=$(git describe --long | cut -f 1 -d "-") buildPatch=$(git describe --long | cut -f 2 -d "-") buildHash=$(git describe --long | cut -f 3 -d "-") buildVersion="$buildTag.$buildPatch" # # Now, we will be raising a build-counter # echo "Raising build-counter" versionPlist=${SRCROOT}/Version.plist buildNumber=$(/usr/libexec/PlistBuddy -c "Print CFBuildNumber" $versionPlist) buildNumber=$((buildNumber+1)) echo "build version will be: $buildVersion.$buildNumber" # # Stashing some of the freshly gathered information # /usr/libexec/PlistBuddy -c "Set :CFBuildNumber $buildNumber" $versionPlist /usr/libexec/PlistBuddy -c "Set :CFBuildVersion $buildVersion" $versionPlist /usr/libexec/PlistBuddy -c "Set :CFBuildHash $buildHash" $versionPlist # # This one renders a version.h-file containing the version-identifier as preprocessor define # echo "Rendering version.h" echo "#define FRAMEWORK_VERSION @\"$buildVersion.$buildNumber ($buildHash)\"" > ${SRCROOT}/version.h

Now drag this Run Script to the second build-phase, just after Target Dependencies.

Add another Run Script to the build process.

Enter the following script


################################################################### # Cellular Documented Framework Build Script # Author: Till Toenshoff, Cellular GmbH # Based on the work of Eonil, Adam Martin, Oliver Drobnik, Netytan ################################################################### # ######################## # UNIVERSAL LIBRARY ######################## # original: http://stackoverflow.com/questions/3520977/build-fat-static-librarydevice-simulator-using-xcode-and-sdk-4 # # Author: Adam Martin - http://twitter.com/redglassesapps # Based on: original script from Eonil (main changes: Eonil's script WILL NOT WORK in Xcode GUI - it WILL CRASH YOUR COMPUTER) # # More info: see this Stack Overflow question: http://stackoverflow.com/questions/3520977/build-fat-static-library-devicesimulator-using-xcode-and-sdk-4 #####################[ part 1 ]################## # First, work out the BASESDK version number (NB: Apple ought to report this, but they hide it) # (incidental: searching for substrings in sh is a nightmare! Sob) SDK_VERSION=$(echo ${SDK_NAME} | grep -o '.\{3\}$') # Next, work out if we're in SIM or DEVICE if [ ${PLATFORM_NAME} = "iphonesimulator" ] then OTHER_SDK_TO_BUILD=iphoneos${SDK_VERSION} else OTHER_SDK_TO_BUILD=iphonesimulator${SDK_VERSION} fi echo "XCode has selected SDK: ${PLATFORM_NAME} with version: ${SDK_VERSION} (although back-targetting: ${IPHONEOS_DEPLOYMENT_TARGET})" echo "...therefore, OTHER_SDK_TO_BUILD = ${OTHER_SDK_TO_BUILD}" # #####################[ end of part 1 ]##################

#####################[ part 2 ]################## # # IF this is the original invocation, invoke WHATEVER other builds are required # # Xcode is already building ONE target... # # ...but this is a LIBRARY, so Apple is wrong to set it to build just one. # ...we need to build ALL targets # ...we MUST NOT re-build the target that is ALREADY being built: Xcode WILL CRASH YOUR COMPUTER if you try this (infinite recursion!) # # # So: build ONLY the missing platforms/configurations. if [ "true" = ${ALREADYINVOKED} ] then echo "RECURSION: I am NOT the root invocation, so I'm NOT going to recurse" else # CRITICAL: # Prevent infinite recursion (Xcode sucks) export ALREADYINVOKED="true" echo "RECURSION: I am the root ... recursing all missing build targets NOW..." echo "RECURSION: ...about to invoke: xcodebuild -configuration \"${CONFIGURATION}\" -target \"${TARGET_NAME}\" -sdk \"${OTHER_SDK_TO_BUULD}\" ${ACTION} RUN_CLANG_STATIC_ANALYZER=NO" xcodebuild -configuration "${CONFIGURATION}" -target "${TARGET_NAME}" -sdk "${OTHER_SDK_TO_BUILD}" ${ACTION} RUN_CLANG_STATIC_ANALYZER=NO fi ACTION="build" #Merge all platform binaries as a fat binary for each configurations. CURRENTCONFIG_DEVICE_DIR=${SYMROOT}/${CONFIGURATION}-iphoneos CURRENTCONFIG_SIMULATOR_DIR=${SYMROOT}/${CONFIGURATION}-iphonesimulator CURRENTCONFIG_UNIVERSAL_DIR=${SYMROOT}/${CONFIGURATION}-universal # ... remove the products of previous runs of this script # NB: this directory is ONLY created by this script - it should be safe to delete! rm -rf "${CURRENTCONFIG_UNIVERSAL_DIR}" mkdir "${CURRENTCONFIG_UNIVERSAL_DIR}" # echo "lipo: for current configuration (${CONFIGURATION}) creating output file: ${CURRENTCONFIG_UNIVERSAL_DIR}/${EXECUTABLE_NAME}" lipo -create -output "${CURRENTCONFIG_UNIVERSAL_DIR}/${EXECUTABLE_NAME}" "${CURRENTCONFIG_DEVICE_DIR}/${EXECUTABLE_NAME}" "${CURRENTCONFIG_SIMULATOR_DIR}/${EXECUTABLE_NAME}" echo "Done with creating a universal library

######################## # FRAMEWORK ######################## # original: http://www.cocoanetics.com/2010/05/making-your-own-iphone-frameworksin-xcode/ echo "Building framework..." # name and build location FRAMEWORK_NAME=${PROJECT_NAME} FRAMEWORK_BUILD_PATH="${PROJECT_DIR}/build/Framework" # these never change FRAMEWORK_VERSION=A FRAMEWORK_CURRENT_VERSION=1 FRAMEWORK_COMPATIBILITY_VERSION=1 # Clean any existing framework that might be there if [ -d "$FRAMEWORK_BUILD_PATH" ] then echo "Framework: Cleaning framework..." rm -rf "$FRAMEWORK_BUILD_PATH" fi # Build the canonical Framework bundle directory structure echo "Framework: Setting up directories..." FRAMEWORK_DIR=$FRAMEWORK_BUILD_PATH/$FRAMEWORK_NAME.framework export FRAMEWORK_DIR mkdir -p $FRAMEWORK_DIR mkdir -p $FRAMEWORK_DIR/Versions mkdir -p $FRAMEWORK_DIR/Versions/$FRAMEWORK_VERSION mkdir -p $FRAMEWORK_DIR/Versions/$FRAMEWORK_VERSION/Resources mkdir -p $FRAMEWORK_DIR/Versions/$FRAMEWORK_VERSION/Headers echo "Framework: Creating symlinks..." ln -s $FRAMEWORK_VERSION $FRAMEWORK_DIR/Versions/Current ln -s Versions/Current/Headers $FRAMEWORK_DIR/Headers ln -s Versions/Current/Resources $FRAMEWORK_DIR/Resources ln -s Versions/Current/$FRAMEWORK_NAME $FRAMEWORK_DIR/$FRAMEWORK_NAME echo "Framework: Copying library..." cp "${CURRENTCONFIG_UNIVERSAL_DIR}/${EXECUTABLE_NAME}" "$FRAMEWORK_DIR/Versions/Current/$FRAMEWORK_NAME" echo "Framework: Copying assets into current version..." cp ${SRCROOT}/*.h "$FRAMEWORK_DIR/Headers"

echo "Fetching build version info" versionPlist=${SRCROOT}/Version.plist buildVersion=$(/usr/libexec/PlistBuddy -c "Print CFBuildVersion" $versionPlist) buildNumber=$(/usr/libexec/PlistBuddy -c "Print CFBuildNumber" $versionPlist) buildHash=$(/usr/libexec/PlistBuddy -c "Print CFBuildHash" $versionPlist) echo "Rendering info.plist" buildPlist=$FRAMEWORK_DIR/Resources/Info.plist cat "${SRCROOT}/Framework.plist" | sed 's/${PROJECT_NAME}/'"${PROJECT_NAME}"'/' > $buildPlist /usr/libexec/PlistBuddy -c "Set :CFBundleVersion $buildVersion.$buildNumber" $buildPlist /usr/libexec/PlistBuddy -c "Set :CFBundleShortVersionString $buildVersion.$buildNumber" $buildPlist /usr/libexec/PlistBuddy -c "Set :CFBuildHash $buildHash" $buildPlist echo "Copying results" if [ ! -d "${CELLULAR_FRAMEWORKS_PATH}/${PROJECT_NAME}" ]; then mkdir -p "${CELLULAR_FRAMEWORKS_PATH}/${PROJECT_NAME}" fi cp -rf $FRAMEWORK_DIR "${CELLULAR_FRAMEWORKS_PATH}/${PROJECT_NAME}" exit 0

Note: This script will install the framework within your CellularFrameworks folder as defined below. Go to Xcode->Preferences->Source Trees and add a new variable using that plusicon on the bottom of the screen.

CELLULAR_FRAMEWORKS_PATH should point towards the location you intend to use for keeping a distributable version of your framework

Additional Files
Re/Sources

Add a new Property List resource

Name it Framework.plist

Make sure it contains the following


<?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>English</string> <key>CFBundleExecutable</key> <string>${PROJECT_NAME}</string> <key>CFBundleIdentifier</key> <string>com.yourcompany.${PROJECT_NAME}</string> <key>CFBundleInfoDictionaryVersion</key> <string>6.0</string> <key>CFBundlePackageType</key> <string>FMWK</string> <key>CFBundleShortVersionString</key> <string></string> <key>CFBundleSignature</key> <string>????</string> <key>CFBundleVersion</key> <string></string> <key>CFBuildHash</key> <string></string> </dict> </plist>

Add another, new Property List resource

Name it Version.plist

Enter the following into the new Version.plist-file


<?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>CFBuildHash</key> <string></string> <key>CFBuildNumber</key> <string>1</string> <key>CFBuildTag</key> <string></string> <key>CFBuildVersion</key> <string></string> </dict> </plist>

Add a new Header file named version.h to your project actually, this step is optional as the file will be created/rendered by the build-process. This is just for making sure that you make a note of that header-file and the ongoing changes within. Just make sure that you do not enter anything of value into this header as it will be rewritten on each and every build.

Your project browser should now look somewhat like the following

Making use of that Version information


Main definition file preprocessor Define
Edit the project main header file (CellularDebugging.h within this example).
#undef FRAMEWORK_VERSION #import "version.h" #define CELLULARMOVIEPLAYER_VERSION extern NSString * const kCellularDebuggingVersion;

FRAMEWORK_VERSION

Main implementation file constant String


Edit the project main implementation file (CellularDebugging.m within this example).
#undef FRAMEWORK_VERSION #import "version.h" NSString * const kCellularDebuggingVersion = FRAMEWORK_VERSION;

The Build Process


Debugging
For testing, build a regular Debug version (Build For Testing). Note: It does not matter whether you select Device or Simulator building as the buildscripts will always build both.

Distribution
For distribution. build an Archive version (Build For Archiving). Note: It does not matter whether you select Device or Simulator building as the buildscripts will always build both.

Rendering a Documentation
Building

Add a new Target

Make it an Aggregate Target

Name the Product

Select the new Targets Build Phases Add a new Build Run Script as done before.

Enter the following script:


echo "Fetching build version info" versionPlist=${SRCROOT}/Version.plist buildVersion=$(/usr/libexec/PlistBuddy -c "Print CFBuildVersion" $versionPlist) buildNumber=$(/usr/libexec/PlistBuddy -c "Print CFBuildNumber" $versionPlist) buildHash=$(/usr/libexec/PlistBuddy -c "Print CFBuildHash" $versionPlist) buildTag=$(/usr/libexec/PlistBuddy -c "Print CFBuildTag" $versionPlist) ######################## # DOXYGEN ######################## # Preconditions: # - needs DoxyGen to be installed # see http://www.stack.nl/~dimitri/doxygen/download.html#latestsrc # - needs a doxygen.config within the source-root-folder # - needs a custom environment variable setup within the project build-settings called DOXYGEN_PATH # see http://developer.apple.com/tools/creatingdocsetswithdoxygen.html echo "running documentation build scripts..." # Build the doxygen documentation for the project and load the docset into Xcode. # Use the following to adjust the value of the $DOXYGEN_PATH User-Defined Setting: # Binary install location: /Applications/Doxygen.app/Contents/Resources/doxygen # Source build install location: /usr/local/bin/doxygen # If the config file doesn't exist, run 'doxygen -g $SOURCE_ROOT/doxygen.config' to # a get default file. if ! [ -f $SOURCE_ROOT/doxygen.config ] then echo doxygen config file does not exist $DOXYGEN_PATH -g $SOURCE_ROOT/doxygen.config fi # Append the proper input/output directories and docset info to the config file. # This works even though values are assigned higher up in the file. Easier than sed. cp $SOURCE_ROOT/doxygen.config $TEMP_DIR/doxygen.config echo "SOURCE_ROOT = $SOURCE_ROOT"; echo "TEMP_DIR = $TEMP_DIR"; echo "DOXYGEN_PATH = $DOXYGEN_PATH"; echo "INPUT = $SOURCE_ROOT" >> $TEMP_DIR/doxygen.config echo "OUTPUT_DIRECTORY = $SOURCE_ROOT/DoxygenDocs.docset" >> $TEMP_DIR/doxygen.config echo "GENERATE_DOCSET = YES" >> $TEMP_DIR/doxygen.config echo "DOCSET_BUNDLE_ID = de.cellular.$PROJECT_NAME" >> $TEMP_DIR/doxygen.config echo "PROJECT_NUMBER = $buildVersion.$buildNumber ($buildHash)" >> $TEMP_DIR/doxygen.config

# Run doxygen on the updated config file. # Note: doxygen creates a Makefile that does most of the heavy lifting. echo "running doxygen..." $DOXYGEN_PATH $TEMP_DIR/doxygen.config # make will invoke docsetutil. Take a look at the Makefile to see how this is done. echo "make doxygen html install..." make -C $SOURCE_ROOT/DoxygenDocs.docset/html install exit 0

Make sure the variable DOXYGEN_PATH is setup properly in your Xcode preferences. Go to Xcode->Preferences->Source Trees and add a new variable using that plusicon on the bottom of the screen.

DOXYGEN_PATH should point towards the doxygen executeable normally located at /Applications/Doxygen.app/Contents/Resources/doxygen Add another Run Script
## ## Copy results to public location ## echo "Copying results..." cp -rf "/Users/${USER}/Library/Developer/Shared/Documentation/DocSets/de.cellular.$PROJE CT_NAME.docset" "${CELLULAR_FRAMEWORKS_PATH}/${PROJECT_NAME}" echo "Done copying results"

This script bundles the Framework with its documentation within the installation folder.

DoxyGen
Create a DoxyGen config file using the DoxyGen GUI frontend Wizard

Enter a proper working directory, project name, project synopsis. Enter . for the Source code directory.

Select Documented entities only and Optimize for C++ output.

Check HTML and uncheck LaTeX

Select Use built-in class diagram generator Now switch over to the Expert tab and select HTML.

Make sure that GENERATE_DOCSET is checked. You may now specify proper DOCSET-metadata like FEEDNAME, BUNDLE_ID, PUBLISHER_ID and PUBLISHER_NAME. Save the DoxyGen configuration file within your source-root-folder and name it doxygen.config.

For an initial test, you may now select Run within DoxyGen. The results should look somewhat like the following;

If all went smooth, you may now go ahead and try to full monty

Вам также может понравиться