Compiling and Releasing ManaPlus for Mac OSX

This is a beginner’s guide to help new OSX developers quickly grasp the Xcode project included with ManaPlus. If you are experienced with Xcode, please feel free to skip directly to the end (Problems and Improving the Workflow). If you wish to help with the releases of ManaPlus, you are reading the right thing. I will try my best to be thorough and make sure I cover most of what I did, what needs to be done and what could be improved. I am no expert on building software for the Mac, so please be patient and point out anything stupid I did.

The Xcode Project

First you will need to clone the git repository. The project is located inside the Xcode folder. There are two targets, which you can select using the Scheme menu on the top of the Xcode interface. The build for Snow Leopard is different because there were crashes with gettext, so it doesn’t have translations enabled (Build Settings > Other C++ Flags > -DENABLE_NLS). Before doing anything, you will probably want to check if there are new c++ files added or removed. Unfortunately, the project has “hard links” to files, so you have to manually add new files. This could be improved I guess, but it isn’t very complicated.

Updating the sources

The easiest way to check for new/removed files is to diff the src/CMakeList.txt. Inside Xcode, there is a handy diff viewer for git. Go to File > Source Control > Repositories… Here you will see a list of local repositories you have. Select the manaplus folder (blue icon) and in the browser, expand src and click on CMakeList.txt. In the bottom area you will see a bunch of commits over time. You have to analyse when was the last update of the Xcode project, and if there where changes since then. If so, expand the commit messages that have changed. Here you will see a list modifications. What you want is the A and D lines (Added and Deleted files).

Now you can right click on the ManaPlus Folder in the Project Browser and select ‘add files to “ManaPlus” …’ You can navigate to the src folder and add the needed files. You will see that they are darker, since Xcode keeps track of the files in your project. It is a good indicator you are doing the right thing!

If files were removed, they will be red inside your ManaPlus folder. Simply delete them.

Updating localization

No need to do anything special here, I have include an automatic script to automagically compile the localizations. Make sure you have gettext installed through macports.

Now that everything is up to date, you can try to hit Run to test out the build. If it fails, it is probably because you didn’t add everything needed, or that a new library has been added (see Working with libraries).

Your app is located inside the Derived Data folder Xcode creates. To find it, go to Xcode > Preferences… > Locations and click the little arrow beside the Derived Data path. You can also change the path, and I highly recommend adding to your finder favorites. Navigate to the Build/Products/Debug/ and there is your debug app.

Building a Release

If all went well and the app works fine, use Product > Build For > Archive to create a release build. It will be located in the release folder next to your debug app. Do this for both ManaPlus and ManaPlus-10.6.

I used DMGCanvas to create the .dmg image. You can use the software free, with limited functionality. It is quite enough for what we need. Open both release.dmgcanvas and release 10.6.dmgcanvas. The file is probably not pointing to the correct application location. Select the ManaPlus.app and in the file info panel, point it to the correct release path (do the same for the 10.6 version). What is nice with DMGCanvas is that from now on, you wont need to do anything else. It will always include the latest version of your built app.

Click build and save your dmg files somewhere. Upload and hopefully all should work. Congratulations, you have built a release for ManaPlus on OS X!

Working with libraries

Libraries are probably one of the trickiest part of releasing a build. I have included all the modified dynamic libraries (.dylib) and static ones (.a) inside the Xcode/libs folder. You will need to download the SDL, SDL_mixer, SDL_image, SDL_net and SDL_ttf frameworks separately and add these in your /Library/Frameworks folder.

If for some reason, the ManaPlus project adds a new dependency, you will have many options to choose from. You can find the source code of the library and compile it inside a framework using Xcode. Then add this framework to your project and also add it to the Build Phases > Copy Files area (the framework one).

I personnally used MacPorts to compile the desired libraries. You can do

sudo port install libname

and the library will be located inside your /opt/local/lib folder. There may be both a dynamic library and a static one (.dylib and .a respectively). If you don’t want headaches, use the static library. Still, I will explain for both, and the issues of dynamic libraries in case a static version is not available.

If you are using a dynamic library (for whatever crazy reason), read on. If you have a static library and are mentally sane, skip to the Static Libraries section.

Dynamic libraries

Before diving in the fun stuff, once your library is installed through macports, you need to copy it from its /opt/local/lib location to the Xcode/libs folder. Now you can simply drag the library inside your Xcode Project in the Frameworks section (you will see there are already many there). It should automatically be added to the Summary > Linked Libraries section and also the Build Phases > Link Binary With Libraries area. You will now have to add it to the Build Phases > Copy Files section that copies the files to Shared Support (not the Frameworks one).

So here is the problem, which I learned the hard way. Dynamic libraries have a fixed path. Simply put, inside of them, they have a location to tell OSX where to find them. To see this, in the terminal do

otool -L libraryname.dylib

For example, from the manaplus directory:

cd Xcode/libs
otool -L libcurl.4.dylib

will output:

	@executable_path/../SharedSupport/libcurl.4.dylib (compatibility version 7.0.0, current version 7.0.0)
	/usr/lib/libssl.0.9.8.dylib (compatibility version 0.9.8, current version 47.0.0)
	/usr/lib/libcrypto.0.9.8.dylib (compatibility version 0.9.8, current version 47.0.0)
	/System/Library/Frameworks/LDAP.framework/Versions/A/LDAP (compatibility version 1.0.0, current version 2.4.0)
	/System/Library/Frameworks/Kerberos.framework/Versions/A/Kerberos (compatibility version 5.0.0, current version 6.0.0)
	/usr/lib/libz.1.dylib (compatibility version 1.0.0, current version 1.2.5)
	/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 169.3.0)

As you can see from the first line, this library was edited by myself. What I did here, using the install_name_tool, is to change the location where our application will look for the file. Since the files are packaged inside the ManaPlus.app/Contents/SharedSupport, I need to tell OSX to look for them there (and not /opt/local/lib).

To do so, simply run this command on YourNewLibrary.dylib:

install_name_tool -id @executable_path/../SharedSupport/YourNewLibrary.dylib YourNewLibrary.dylib

Inside the Xcode project, you will also have to add a line to the Build Phases > Run Scripts area. Here, you will see that every library needed by the project is processed when you build ManaPlus. Basically, without going into to much details, you can copy/past one of the existing lines and simply change the values you need. For example change:

install_name_tool -change /usr/lib/libcurl.4.dylib @executable_path/../SharedSupport/libcurl.4.dylib "$TARGET_BUILD_DIR/$PRODUCT_NAME.app/Contents/MacOS/$PRODUCT_NAME"

to:

install_name_tool -change /usr/lib/YourNewLibrary.dylib @executable_path/../SharedSupport/YourNewLibrary.dylib "$TARGET_BUILD_DIR/$PRODUCT_NAME.app/Contents/MacOS/$PRODUCT_NAME"

Be sure to change /usr/lib to /opt/local/lib if that is where your library was pointing to (if you installed the lib using MacPorts for example). Now things may get a bit complicated (if it wasn’t already). First of all, libraries in /usr/lib are libraries included by default on OSX. You need to analyse the compatibility version of the libraries included with older versions of OS X.

If we take libcurl.4 for example, I had to include it because OS X 10.6 doesn’t provide the compatibility version 7.0.0. Now this is all good, but the library itself has dependencies. Fortunately, Snow Leopard provided all the dependencies at the correct compatibility vesions, so I didn’t have to include libcurl’s dependencies inside the application bundle. Lets look at some other scenario…

Still in the Xcode/libs folder, run:

otool -L libSDL_gfx.14.dylib

Which will output:

	@executable_path/../SharedSupport/libSDL_gfx.14.dylib (compatibility version 24.0.0, current version 24.1.0)
	@executable_path/../SharedSupport/libSDL-1.2.0.dylib (compatibility version 12.0.0, current version 12.4.0)
	@executable_path/../SharedSupport/libXrandr.2.dylib (compatibility version 5.0.0, current version 5.0.0)
	@executable_path/../SharedSupport/libXext.6.dylib (compatibility version 11.0.0, current version 11.0.0)
	@executable_path/../SharedSupport/libXrender.1.dylib (compatibility version 5.0.0, current version 5.0.0)
	@executable_path/../SharedSupport/libX11.6.dylib (compatibility version 10.0.0, current version 10.0.0)
	@executable_path/../SharedSupport/libxcb.1.dylib (compatibility version 3.0.0, current version 3.0.0)
	@executable_path/../SharedSupport/libXau.6.dylib (compatibility version 7.0.0, current version 7.0.0)
	@executable_path/../SharedSupport/libXdmcp.6.dylib (compatibility version 7.0.0, current version 7.0.0)
	/System/Library/Frameworks/Cocoa.framework/Versions/A/Cocoa (compatibility version 1.0.0, current version 19.0.0)
	/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 169.3.0)

I had to add all the necessary dependencies to the project, because they where all pointing to /opt/local/lib, which means they were installed through macports, and a user wont have these libraries available. As a rule of thumb, if the lib or dependency is in /usr/lib, then it should be fine. If the library is pointing too /opt/local/lib you will have to include its dependencies that are also pointing to /opt/local/lib. You can do it all manually, or use shell scripts provided in the libs folder (libs-change.sh and libs-id.sh). I found these on the internet but can’t recover the source…

So first, add your library .dylib and all its dependencies to a temporary folder, and make sure they don’t have more dependencies. Also copy the shell scripts inside this folder.

To change all the ids of your dylibs inside the folder to @executable_path/../SharedSupport/, run the libs-id.sh script:

./libs-id.sh

For the other script, first uncomment the first part and comment the second, and run it the same way you did with libs-id. This will create a text file called changes. You can review the changes that are to be made to make sure all is working. Then, comment the first part of the script, uncomment the second and run it again (./libs-change.sh). Now all your /opt/local/lib dependencies should be changed to @executable_path/../SharedSupport/. Hurray!

To view the result, you can run

otool -L *

inside your folder, you will see all your libraries information. If all is well, add these to the Xcode/lib folder and also drag the dependencies inside your Xcode project in Frameworks/dependencies. You do not need to add a script line to Build Phases > Run Scripts for dependencies. Only for the one library ManaPlus now requires (which you did earlier).

Whew! What a ride. Now you can check out the length of my Static Library section… To see how all of this isn’t require when you use a .a library.

Static Libraries

So you are sane and have decided to use Static Libraries. I learned about these after going through the pain of linking all my macports dynamic libraries [sigh]. So lets get started. You have a NewLibrary.a inside your /opt/local/lib directory. Copy that file to the Xcode/lib folder inside the manaplus directory. Take the file and drag it into your Xcode project in the Frameworks section. It should automatically be added to the Summary > Linked Libraries section and also the Build Phases > Link Binary With Libraries area. You need to remove it from the Build Phases > Link Binary With Libraries section. This is because Xcode is broken, and if you leave it there, Xcode will point to a .dylib if it can find one (for more information on this, you can google ‘perian-and-xcode-dylib-f.e.t.i.s.h’ and remove the dots, the wiki doesn’t like that word).

You will also have to add the full path to the library in the Build Settings > Other Linker Flags section as so:

$(SRCROOT)/libs/NewLibrary.a

Just add it before or after the existing ones. The $(SRCROOT) will transform to the correct path to your project.

Et voila! You are done. The library will get included inside the application bundle, along with everything it needs (I don’t think you even need to add its dependencies…). Now your glad you didn’t use a dynamic library, aren’t you.

Conclusion

Use static libraries.

Oh, and finally, to make sure all went well, compile the application, navigate to its location. Right click and select Show Package Content. Navigate to the executable (Contents/MacOS/ManaPlus). In terminal, type ‘otool -L ' with the space, and drag the ManaPlus executable onto the terminal to get its full path. Now press enter, you should not see any library that points to /opt/local/lib, and if you used a static library, you shouldn’t see it either.

Problems and Improving the Workflow

If you wish to improve the building experience, here are some good places to start.

  • Find a way to reference the src directory, instead of hard-linking to the source files. This would let us skip the “finding and removing files” phase.

* Create a bash script in the Build Phases to compile the localization files and copy them inside the app bundle. This would allow us to forget about dragging new translations inside the locale folder. It would also ensure everything is always up to date. done

That’s pretty much it for now!

Print/export
Languages