Friday, June 11, 2010

Integrating Doxygen with Xcode

I recenty went looking for a good tool to document my Cocoa project and quickly found out that there seem to be two widely used documentation generators for Objective C:
  • HeaderDoc - A Documentation utility maintained by Apple.
  • Doxygen - A widely used open source document generator.
I decided to go with Doxygen both because It seems to have more features than HeaderDoc and because I have used it in the past. Xcode does not have support for Doxygen out of the box which means one is in for a tedious setup procedure. There is a tutorial on Apple's developer site which takes you through a few different ways of using Doxygen to generate documentation with Xcode ranging from using Doxygen's external GUI tool fully integrating Doxygen with Xcode using docsets. I decided that I wanted full integration since this allows you to right click on your own classes and select "Find Text in Documentation".

Unfortunately there are a few problems with Apple's tutorial. Firstly, the Run-Script they provide fails if you have a space in your project name or any of the file system paths you are using. Secondly, I didn't like the way the script configures Doxygen. Thirdly, the script in Apple's tutorial simply appends lines to the Doxygen configuration file which means those lines are duplicated in the file.

Step 1. Installing Doxygen
There are two ways to instally Doxygen. You can either install the Doxygen GUI application for OS X or you can also compile Doxygen from source. Binaries, sources and compile instructions are available on the Doxygen the project downloadpage.

Step 2 - Xcode integration
Once you have installed Doxygen on your Mac you have to configure Xcode. There are a few tasks you must perform:
  1. Create a new Xcode Target and an associated Run-Script item which you’ll use to trigger document generation.
  2. Add a couple of User-Defined Settings (a couple of environmental variables the shell script embedded  in the Run-Script item will read).
  3. Install the script text into the Run-Script item.
  4. Modify the Doxygen configuration options in the script.

Step 2.1 - The Run-Script
I rewrote the script from Apples website so that it can now handle spaces in directory paths. It also uses sed for substitution rather than appending lines to the script. You must donwload the script before proceeding.
Once you have dowlnoaded the script you have to create a new Xcode target. Unfold the “Targets” item in the Xcode project navigator, right click it and select “Add > New Target...”.


In the “New Target” window select “Other” and “Shell Script Target” and click “Next”. This should take you to a new window. Name the target “Generate Docs” and click Finish.

You could also have created this Run-Script item under the Xcode Target that builds your application. This has the advantage that the documentation gets built every time you build your app. There are two downsides to this approach:
  1. It can take a long time to generate the documentation and rebuilding it every time you build the app is unnecessary and lengthens your build time even when you didn’t modify your Doxygen comments.
  2. For some reason it is sometimes necessary to restart Xcode before it becomes completely aware of the new docset. You can still activate the docset and browse it in the Xcode documentation browser but the search function may not work optimally.
By putting the documentation generation in a separate target you can re-build your docset only when necessary by selecting the correct target from the  drop down box at the top of the main Xcode window and pressing the Build button.





Step 2.2 - Create User-Defined Settings
When you closed the “New Target” wizard Xcode should have taken you to the “Target ‘Generate Docs’ Info” window. If it didn’t you can right click on you new Xcode Target and select “Get Info”. In the Info window select the “Build” tab and add a couple of new User-Defined setting using the drop down box in the lower left corner of the window.


Create the following User-Defined Settings:
  • DOXYGEN_DOCSET_BUNDLE_ID - This will become the name of the docset bundle, for example like a java style package name: 'com.yourorganization.yourproduct'.
  • DOXYGEN_PATH - This setting points the script to the location of the Doxygen command-line binary. Binary install locations are:
    • If you downloaded the Doxygen GUI app: /Applications/Doxygen.app/Contents/Resources/doxygen
    • If you build Doxygen from Source: /usr/local/bin/doxygen
Once you have created the setting close the Info window.


Step 2.3 - Script installation
Expand the “Generate Docs” target you just created, right click it and select “Get Info”.  Now open the script you downloaded above, highlight it and copy the script text and paste it into the script field in the Info window.

Step 2.4 - Modify Doxygen settings in the script
The settings in my version of this script are a bit different from the ones in the script provided by Apple which pretty much uses the default Doxygen configuration and which does not behave the way I want it to. With the default settings Doxygen will document methods that I want to keep hidden from the user. So far the only way I have discovered to change this behaviour is to make sure that the following Doxygen configuration options are set as follows:
  • EXTRACT_ALL = NO
  • HIDE_UNDOC_MEMBERS  = YES
  • HIDE_UNDOC_CLASSES     = YES

This causes Doxygen to hide all classes, methodd and other constructs that are not explicitly documented with comments. This also forces the user to explicitly comment/document all classes, methods and other constructs that he wants included in the docset.

Changing these settings as follows:
  • EXTRACT_ALL = NO
  • HIDE_UNDOC_MEMBERS  = NO
  • HIDE_UNDOC_CLASSES     = NO
Will cause Doxygen to include undocumented constructs but it will include methods in private interfaces which forces the user to explicitly instruct Doxygen to ignore them using the @cond command.

Setting EXTRACT_ALL = NO will cause  the settings for HIDE_UNDOC_MEMBERS and HIDE_UNDOC_CLASSES to be ignored and causes Doxygen to include absolutely everything in the docset it produces. This includes summaries for *.m files but excluding  private and static file members.


There are a few more interesting configuration options:
  • REPEAT_BRIEF = YES - Repeat @brief descriptions in the extended class and method descriptions.
  • JAVADOC_AUTOBRIEF = YES - Insert @brief descriptions into the class member list at the top of each class reference page.
  • INLINE_INHERITED_MEMB = YES - Causes inherited methods to be included in the class reference pages for child classes.
For more information on Doxygen configuration options see here.

Quirks
In conclusion I thought I’d include a few Doxygen quirks that I discovered while using this setup.

  • If you use HeaderDoc style comments (/*! comment */) the body of the comment will be assumed to be an extended description, if you use Doxygen/Javadoc style tags (/** comment */) the body of the comment will is assumed to be a @brief description. This means that if JAVADOC_AUTOBRIEF = YES, REPEAT_BRIEF = NO and you are using Doxygen style comments the method description is only inserted into the javadoc autobrief list. To get the brief in both places set YES, REPEAT_BRIEF = YES.
  • Doxygen puts documentation for #define clauses into the header file description. If you set EXTRACT_ALL = NO you will find that header file documentation is only generated if you insert the following Doxygen commands into the header file: 

                 /** @file FooClass.h
                       @brief testing defines
                        This is to test the documentation of defines.
                  */

                  @interface FooClass : NSObject{
                        double barVar;
                  }

                  /** @def KSGL_VECTOR_CLOCKW
                        @brief clockwise rotation.
                  */
                  #define SOME_DEFINE 1