Unity: Automate Post Process
Apr 11, 2013
Everytime after I build a Xcode project from Unity for PigRush, I need to manually link some system frameworks, some third-party frameworks, add my native code and change some build settings. It is really a pain in the ass. After struggling with Unity’s Build Player Pipeline, finally I got process automated. The problems I found during process should cover lots of scenarios you probably will run into and hopefully this will give you some lights :)
##Case Intro So I have custom libraries and native code reside under ~/UnityWorkspace/PigRush-iOS/Assets/ObjC, which includes following file structure:
├── Chartboost
│ ├── CBAnalytics.h
│ ├── CBAnalytics.h.meta
│ ├── Chartboost.h
│ ├── Chartboost.h.meta
│ ├── libChartboost.a
│ └── libChartboost.a.meta
├── Chartboost.meta
├── FlurryAnalytics
│ ├── FlurryAnalytics.h
│ ├── FlurryAnalytics.h.meta
│ ├── libFlurryAnalytics.a
│ └── libFlurryAnalytics.a.meta
├── FlurryAnalytics.meta
├── RevMobAds.framework
│ ├── Headers -> Versions/Current/Headers
│ ├── Resources -> Versions/Current/Resources
│ ├── RevMobAds -> Versions/Current/RevMobAds
│ ├── Versions
├── PigRushNavtiveCode
│ ├── Appirater.h
│ ├── Appirater.h.meta
│ ├── Appirater.m
│ ├── Appirater.m.meta
│ ├── EmailComposer.h
│ ├── EmailComposer.h.meta
│ ├── EmailComposer.m
│ ├── EmailComposer.m.meta
│ ├── …blablabla
│ ├── UnityNativeManager.h
│ ├── UnityNativeManager.h.meta
│ ├── UnityNativeManager.mm
│ └── UnityNativeManager.mm.meta
The file structure above is not all, it is part of it.
And the post build process that I’m handling is like following:
- Import the system frameworks
- Accounts.framework (optional)
- GameKit.framework (optional)
- MessageUI.framework
- MobileCoreServices.framework
- StoreKit.famework
- Social.framework (optional)
- libsqlite3.dylib
- Drag into the project all files and folders I listed above from ~/UnityWorkspace/PigRush-iOS/Assets/ObjC
- run find . -name ‘.meta’ -type f -delete* to delete .meta files first
-
Modify AppController.h to add following instance variable declarations for UrbanArship push notification
NSString *deviceToken; NSString *deviceAlias; NSString *pushActionURL;
-
Modify AppController.mm to add header imports
#import "Appirater.h" #import "RDGameCenterManager.h" #import "Chartboost.h" #import <RevMobAds/RevMobAds.h>
-
Modify AppController.mm to add [[RDGameCenterManager sharedInstance] disconnectLocalPlayer]; to end of applicationWillResignActive method
-
Modify AppController.mm to add [Appirater appEnteredForeground:YES]; to end of applicationWillEnterForeground method
-
Modify AppController.mm to add other code snippets
- Finally set GCC_ENABLE_OBJC_EXCEPTIONS to YES in BuildSettings
Imagine everytime, you build from Unity and you have to do those steps, it is very paninful process.
##Solution
Note: You can find complete code in my github repo: UnityAutomatePostProcess.
As Unity PostProcessBuildAttribute reference says,
Add this attribute to a method to get a notification just after building the player.
– which means that we can use this meta tag to register with Unity engine to kick off post build process.Also notice:
This is an editor class. To use it you have to place your script in Assets/Editor inside your project folder.
Pretty straightforward.
Given that we’re gonna play around xcode project file, it looks like using python is good option(as it is built-in supported on Mac). So what this callback script is just simply calling our post_process.py. We create a file CustomPostprocessScript.cs under ~/UnityWorkspace/PigRush-iOS/Assets/Editor:
Here the pathToBuildProject is like ~/UnityWorkspace/XCode/PigRush-XCode and objCPath is the path referring to the folder that our custom libraries and native code reside in(~/UnityWorkspace/PigRush-iOS/Assets/ObjC).
Then we dive into our magic post_process.py script.
Code is pretty self-explanatory.
Because that we need to mess around with xcode project file, we better use some existing script to do it. And there is one I found which is pretty good: Mod PBXProj (The script I refere here doesn’t support change build setings and has some problem with escape library search path, you can download good one from my github).
The most complicted code would be in the Step 3, which we modified our AppController. This is place you probably do:
- add instance variables delcarations in AppController.h
- add code snippet to begin of specfic method in AppController.mm
- add code snippet to end of specfic method in AppController.mm
- add methods to end of AppController.mm
Because you probably need modify that file constantly, it would be great there is flexible and easy way to do it.
Let’s look at part of appcontroller.py:
A breif explanation is given if you need to change it later.
-
add code to begin/end of specfic method, you just need to copy the method signature and add to methodSignatures.append(‘some method signature’)
-
if the code snippet is like one line you just do it like this valueToAppend.append(‘[Appirater appEnteredForeground:YES];’) ; and if code snippet is pretty long, you better put it in method like def pushActionInstanceDeclaration(): and append it like this valueToAppend.append(pushActionInstanceDeclaration())
-
to mark the position of begin/end like positionsInMethod.append(“begin/end”)
-
put content to append inside extraCodeToAddInAppControllerMMFile and pass it as fifth parameter of process_app_controller_wrapper
There you go. You can put the code change in your appcontroller.py and it is pretty easy to make changes.
##Fix Library Search Path When I build from unity and run it in xcode, I got bunch of errors. Among them , one says that
ld: library not found for -lChartboost
ld: library not found for -lFlurryAnalytics
But I double checked the chartboost and flurry analytics static lib are indeed imported and showed in Linked Libraries of build settings.After searched on Stackoverflow, I open the build setting of xcode by going to
"Targets"-> "Build Settings" -> "Library Search Paths"
Then you will see following settings:
"$(SRCROOT)"
"$(SRCROOT)/Libraries"
\"$(SRCROOT)/../../PigRush-iOS/Assets/ObjC/Chartboost\"
\"$(SRCROOT)/../../PigRush-iOS/Assets/ObjC/FlurryAnalytics\"
WTF! The paths pointing to our custom libraries are got escaped. Then I drill down the code of mod_pbxproj.py, and found following snippet:
which you probably notice the escape flag by default is set to True. Then I change that flag to False, that error was gone :)
##Done ? Not Yet But I still got lots errors in Xcode and no clue aobut what’s going wrong. Until that I found there is another PostBuildProcess script from Kamcord. Kamcord is unity package we imported for use of record and share mobile gameplay video.
Kamcord also has PostBuildProcess script under Assets/Editor folder, which is like following:
It just executes a perl script, which basically just add Kamcord.framework and related resources to xcode.
Let’s look the Link Library With Libraries, we found that Kamcord.framework is missing! But if we remove our custom script, it is showing.
Now we had two post build process scripts. Pretty bad, as we dont’ know the order it will get executed. And the problem that Kamdcord.framework is missing, maybe is because of orders of execution of scripts.
By taking a look at logs of Unity app, we found actually our CustomPostprocessScript.cs runs before KamcordPostprocessScript.cs. (I will talk about how to check the logs from print in python and UnityEngine.Debug.Log in Unity, which it is handy when debugging).
What I want to do here is make sure our custom script always run after other scripts. Because if we execute our script first, then we have no idea what other script is gonna modify, which possibily screws up everything.
Is there any way specify the order of execution of script ? Yes, from Unity Post Process Mayhem, I found that
[PostProcessBuild(0)] // <- this is where the magic happens
public static void OnPostProcessBuildFirst(BuildTarget target, string path)
{
Debug.Log("I get Executed First");
}
NB: -10 is a higher priority than 100, the default priority is 1
Cool, then we can go back to our CustomPostprocessScript.cs and modify [PostProcessBuild] to [PostProcessBuild(100)]. Then we make sure our script always run after other scripts.
You can see the benefits of coding our post process in a sepearate file. By doing this way, we make sure when we update other package like Kamcord, no matter when they change in their script, it won’t affect our custom script.
##Tips To look the logs of post build process scripts, like print in python, you can follow steps:
open *Console* from spotlight --> left panel FILES *~/Library/Logs* --> expand to *Unity* --> click *Editor.log*
Then you can see logs of Unity. It is also quite usefull when the Unity is freezing, and you want know whether it is really ‘dead’.
Because the xcode project when built from Unity is like 768 M, and it takes Unity 5~10 minutes to build out, which make the process extremely painful.
I’d like to suggest when testing python script, you probably just move a clean copy of AppController or pbproject file to another folder with git supported. Then you can test your script separately without everytime build from Unity.
Python is quite straightford to pick up, like I just spend several mintues to get familiar with its syntax and be able work on it quite easily. BTW, pay attention to the soft tabs and hard tabs when you get indentation problems.
You can have a complete source code here:UnityAutomatePostProcess.
Enjoy unity!