Xcode -> Makefile

Jim Schimpf (vze35xda@verizon.net)(7-Mar-2004)


When I develop on OS X I usually use X Code.  Others obviously differ about use of IDE's but I prefer it.  I do have a problem when I wish to move an app from OS X to other flavors of UNIX or Linux.  In most cases I am not attempting anything exotic, these are usually CLI applications (e.g. a very specialized Lua interpreter with extensions for my company's products).  So moving to Linux or UNIX should be pretty much a slam-dunk, just recompiling the code.  Easy in theory but the app in question has over 100 files, files in many different directories and it's an X Code project so no makefile.  

I had hoped that X Code would have this export function but it didn't, so I created a free open source version is available to you. 

PBTOMAKE Version 3.0 Program

    This program is freeware with source and can be downloaded from (PBTOMAKE Dowload page).  It is run from the the terminal command line and as an example run on its own project you would get:

[Pandoras-Computer:TOMAKE10.3/Source/PBTOMAKE] jim% pbtomake -i PBTOMAKE.pbproj
PRODUCT IS [pbtomake]
FILE [main.cp]
FILE [CPBPROJ.cp]
FILE [CBufFile.cp]
FILE [CSymbol.cp]
FILE [CLexFile.cp]
FILE [CpbxLexFile.cp]
FILE [CTokFile.cp]
FILE [CMaker.cp]
FILE [cutil.cp]
FILE [CBufFile.h]
FILE [tgtypes.h]
FILE [CSymbol.h]
FILE [CLexFile.h]
FILE [CpbxLexFile.h]
FILE [CTokFile.h]
FILE [CMaker.h]
FILE [base.h]
FILE [cutil.h]
FILE [CPBPROJ.h]
[Pandoras-Computer:TOMAKE10.3/Source/PBTOMAKE] jim%

This would generate the make file in a file called makefile. The makefile could be used by typing make in this directory. This would build a new copy of pbtomake using make and the makefile.
[Pandoras-Computer:TOMAKE10.3/Source/PBTOMAKE] jim% make
/usr/bin/g++3 ../main.cp -c -I../BASE_PC -I../BASE_PC/Dohickies -I.. -o main.o
/usr/bin/g++3 ../CPBPROJ.cp -c -I../BASE_PC -I../BASE_PC/Dohickies -I.. -o CPBPROJ.o
/usr/bin/g++3 ../BASE_PC/CBufFile.cp -c -I../BASE_PC -I../BASE_PC/Dohickies -I.. -o CBufFile.o
:
:
/usr/bin/g++3 ../CMaker.cp -c -I../BASE_PC -I../BASE_PC/Dohickies -I.. -o CMaker.o
/usr/bin/g++3 ../BASE_PC/cutil.cp -c -I../BASE_PC -I../BASE_PC/Dohickies -I.. -o cutil.o
/usr/bin/g++3 \
main.o\
CPBPROJ.o\
CBufFile.o\
CSymbol.o\
CLexFile.o\
CpbxLexFile.o\
CTokFile.o\
CMaker.o\
cutil.o\
-o pbtomake
[Pandoras-Computer:TOMAKE10.3/Source/PBTOMAKE] jim%

    Pretty simple really, and the idea is you run pbtomake on your project, copy the whole development tree to the Linux/UNIX box and run make there.  You will then get the application built on the other system without having to manually build a make file with all the dependencies.  Obviously you still have code porting to do since for example under OS X you link to frameworks while under other systems you have other ways.  There's lots of other details involved in porting the code but pbtomake saves you initial messy task of listing all the files and their paths to manually build a makefile.

makefile Details

The makefile created by the program is very simple and has everything specified, It doesn't use rules or any other shortcuts.  Each and every file is separately specified along with all the include files.  So the file created for this run is:

###################################################
#
# Makefile for pbtomake
# Creator [Xcode -> Makefile Ver: 3.00]
# Created: [Sat Mar 6 07:48:15 2004]
#
###################################################
#
# Macros
#
CC = /usr/bin/g++3
CC_OPTIONS =
LNK_OPTIONS =
#
# INCLUDE directories for pbtomake
#

INCLUDE = \
-I../BASE_PC\
-I../BASE_PC/Dohickies\
-I..
#
# Build pbtomake
#
pbtomake : \
main.o\
:
:
cutil.o

$(CC) $(LNK_OPTIONS) \
main.o\
:
:
cutil.o\
-o pbtomake
#
# Build the parts of pbtomake
#

# Item # 1 -- main --
main.o : ../main.cp
$(CC) $(CC_OPTIONS) ../main.cp -c $(INCLUDE) -o main.o
:
:
# Item # 9 -- cutil --
cutil.o : ../BASE_PC/cutil.cp
$(CC) $(CC_OPTIONS) ../BASE_PC/cutil.cp -c $(INCLUDE) -o cutil.o
##### END RUN ####

Program Internals

    Project Files

    The Xcode project file PBTOMAKE.pbproj is really a directory containing two files (do
ls -als PBTOMAKE.pbproj at the command line):
 0 drwxrwxrwx   4 jim  staff    136  7 Mar 07:03 .
0 drwxrwxrwx 18 jim staff 612 6 Mar 07:51 ..
40 -rwxrwxrwx 1 jim staff 17747 7 Mar 07:03 jim.pbxuser
32 -rwxrwxrwx 1 jim staff 12708 7 Mar 07:03 project.pbxproj

    The jim.pbxuser is the per user settings for the project and things like window positions.  The project.pbxproj is the description of the files and how to build the application.  This is the file that pbtomake works on.

    Knowledge of project.pbxproj is just by observation and trying to extract the information to build the makefile.  If someone reading this has deeper knowledge I would appreciate hearing from you.  The general form of both of these files is a hierarchal structure based on keys/values and lists.  The usual form of things are:

    key   =  value;

    Now value can either be a single string/name or a list and lists are written as:

    {
        thing,
        thing1,
         :
    };

    Where thing and thing1 can be either simple values or embedded lists.

    Project File Analysis


    The code modules CBPROJ, CpbxLexFile, CTokFile, CLexFile and CBufFile are used to convert the file structure into an in memory tree representation of the text structure in the file.  The form here is the conventional breaking of the input file into tokens; then CBPROJ.cp builds the tree.

    For example reading  key   =  value; would turn into the token list

     key      =     value    ;
    <Token> <LINK> <Token> <END>

CBPROJ reads each of these tokens, stacking them up until a trigger is found, In this case an <END> token meaning we have come to the end of an element in the file.  It then can package up the key and value as an element and attach it to the tree structure of the file it's building.  If you want to see this tree use the -debug option of pbtomake and it will be dumped out.

Besides the tree structure being built, the program also needs a reference table so it can search this tree for particular keys.  A direct search of the tree might work but, from experience this is very slow.  The module CSymbol is used to create a hashed symbol table of each of the keys found in the file.  In this way, a very quick search can find nth appearance of any key in the file.

    Makefile Construction

The module CMaker is where the rubber meets the road and the actual makefile is constructed.  This module is run after the symbol table and tree representation of the project file are completed.  To build the makefile two things have to be found: a list of the .H files and a list of the .C(or CP) files.  You can see these in the product makefile.  Once you know these then:

    The INCLUDE list comes from the path lists to the .h files
    The main dependency  <name> :  <Filename>.o comes from the list of C/CP files
    The main link list and each of the individual file builds comes from the .c list.

I've found in the project file the VALUE of an isa key of PBXSourcesBuildPhase denotes the list of C files and PBXHeadersBuildPhase denotes the list of H files. It is then a matter of finding these lists in the in memory tree representation of the project file and building file lists.  

The rest of building the makefile is largely a matter of fprintf()'s of the lists of data in various forms to the output makefile.  This is also where if you want another type of makefile it's quite easy to change the output.