« Pipes and redirections in sh | Main | Beach volley quiz »

Nailed it down

Xcode has been driving me mad recently. I am writing TINI applications in Java, which requires me to feed the class files to a converter application. I had a little trouble figuring out how to make Xcode generate and run on a class hierarchy instead of a jar file but I will talk about it in details in an upcoming entry. The topic today is the bug I discovered (and fixed!) in Xcode when working with class hierarchies.

Since I already wrote about it several times for the Java Dev and Xcode Users mailing lists, I will limit myself to posting the report I submitted to Apple.

1. Configuration

  • Mac OS X 10.3.5
  • Xcode 1.5
  • Java 1.4.2 Update 1
  • Java 1.4.2 Update 1 Developer Tools

2. Summary

When configuring a Java Tool project to generate a class hierarchy instead of a Java archive, the class files are copied into TARGET_BUILD_DIR/PRODUCT_NAME (i.e. PRODUCT_CLASS_FILE_DIR) the first time the project is built. However, on subsequent builds, the classes in that folder are not updated, even when the sources are. Therefore, the project runs on old class files and so do potential shell script build phases that would post process the class files.

3. Steps to reproduce

  • Create a new Java Tool project in Xcode;
  • In the active target, change the Java Archive Settings / Product type to Class Hierarchy (or in expert mode, set JAVA_ARCHIVE_CLASSES = NO);
  • Build the project;
  • In the Finder, take note the modification date of the class file in build/PRODUCT_NAME;
  • Make a modification to the sources, e.g. change "Hello World!" to "Take over the World!";
  • Save and build the project;
  • In the Finder, notice that the class file has not been updated since the modification date did not change.

4. Expected results

Every time the project is built, the class hierarchy should be copied from CLASS_FILE_DIR to PRODUCT_CLASS_FILE_DIR if it has been modified, as it happens the first time the project is built.

5. Actual results

The class hierarchy is only copied on the first build or after a target clean-up.

6. Cause

Watching the build process from a shell using xcodebuild instead of the Xcode GUI shows that on a clean target, after the Java Archive Files build phase, the PRODUCT_CLASS_FILE_DIR is created and ditto is used to copy the class files from CLASS_FILE_DIR to PRODUCT_CLASS_FILE_DIR:

BuildPhase <JavaArchiveFiles>Hello
    echo Completed phase "<JavaArchiveFiles>" for "<JavaArchiveFiles>Hello"
Completed phase <JavaArchiveFiles> for <JavaArchiveFiles>Hello

Mkdir […]/Hello/build/Hello 
    /bin/mkdir  -p […]/Hello/build/Hello 

Ditto […]/Hello/build/Hello 
    /usr/bin/ditto  […]/Hello/build/Hello.build/Hello.build\
    /JavaClasses […]/Hello/build/Hello

However, when PRODUCT_CLASS_FILE_DIR exists, neither of these two operations is executed.

7. Workaround

Cleaning the active target prior to building or deleting the PRODUCT_CLASS_FILE_DIR folder causes Xcode to create it again, with up-to-date files. However, emptying this folder is not enough.

8. Resolution

The build instructions are defined in

/Developer/Makefiles/pbx_jamfiles/ProjectBuilderJambase.

The parts of interest are the definition of Mkdir (ProjectBuilderJambase, line 299):

# Mkdir <directory>
# Creates <directory>
rule Mkdir
{
    # Only existence of the directory matters
    NOUPDATE $(1) ;
}
actions together piecemeal Mkdir
{
    $(MKDIR) -p $(1:Q)
}

and the section where the class files are copied using ditto, where it looks like someone has been bothered by this bug for some time (line 4267):

if $(JAVA_ARCHIVE_CLASSES) != YES {
    # !!!:cmolick:20020123 product class file dir not always made?!
    Mkdir $(PRODUCT_CLASS_FILE_DIR) ;
    ProductFile $(PRODUCT_CLASS_FILE_DIR) ;
    Ditto $(PRODUCT_CLASS_FILE_DIR) : $(CLASS_FILE_DIR) ;
if $(MERGED_ARCHIVES) {
        DEPENDS $(PRODUCT_CLASS_FILE_DIR) : $(MERGED_ARCHIVES) ;
    }
    else {
        DEPENDS $(PRODUCT_CLASS_FILE_DIR) : $(JAVA_COMPILE_TARGET) ;
    }
}

It is actually the Mkdir command that prevents the copy to execute, even though mkdir -p exits 0 regardless of whether the directory to create is already present. I have no idea whatsoever of the language used to write ProjectBuilderJambase but I can make a pretty safe guess that the culprit is the NOUPDATE statement in the Mkdir rule. Indeed, removing it solves the problem, albeit not in a very clean way as this statement is probably necessary elsewhere (although it might be problematic elsewhere as well).

The fix I implemented was to create a new action, ForceMkDir, to use in this particular case (ProjectBuilderJambase, line 310):

# ForceMkdir <directory>
# Creates <directory>, even if it already exists.
#
# This modification is for the Ditto step that copies class files in
# Java projects (line 4267-78). The original Mkdir action above seems
# to prevent ditto from executing when the directory already exists.
#
# Corrected 2004-08-12 by Ölbaum, who's quite proud of having fixed a
# glitch in a program written in a language he does not even know the
# name of.
actions together piecemeal ForceMkdir
{
    $(MKDIR) -p $(1:Q)
}

and (line 4269):

# !!!!:Ölbaum:20040812 fixed!
ForceMkdir $(PRODUCT_CLASS_FILE_DIR) ;

9. Enclosures

A patch file for these modifications: ProjectBuilderJambase.diff.

10. Conclusion

After applying these modifications, the class hierarchy in PRODUCT_CLASS_FILE_DIR is properly updated each time the classes are recompiled.

TrackBack

TrackBack URL for this entry: http://ithink.ch/blog/tb.cgi/61.

Make sure JavaScript is enabled before using this URL. If you would like to ping my blog but can't, please do send me an e-mail at os3476 at this domain.

This post explains how I build applications for the TINI using Xcode. It was written for Xcode 2.4. [Read More]

Comments

Do you have a post that is a how-to for doing TINI dev with this stuff in mind?

I’m a PC convert and am hoping to figure out how to do tini in XCode.

I hate to admit it, but I still have to write that post. Last time I did something for the TINI, I used an Ant-based Xcode project and TINIAnt. I don’t find it ideal, though, as build is slow with ant and error reporting not as friendly as it could.

I just had a quick look at Xcode and I finally remember how I did it two years ago. If you are still interested I will give you all the information you need and then use it to write this blog post.

Post a comment

Make sure JavaScript is enabled before posting a comment. If you would like to post a comment but can't, please do send me an e-mail at os3476 at this domain.

Do not meddle in the affairs of Coding Ninjas, for they are subtle and quick to anger.