Automate framework generation for a modular iOS project
Suppose you have a modular project and you need to ship the frameworks for different projects of your workspace to clients. Now, also take into consideration that some of your clients want to have iphoneos frameworks and some want the fat binaries i.e., universal frameworks.
The usual approach is to build the projects for ‘any iOS device’ in Xcode and then send the frameworks, but this is only good if they want iphoneos release. The main problem starts to come if the clients want a universal build, here you have to lipo the executables and then copy the swift modules and also modify the headers for each and every framework, now assume if you have 14 such frameworks, how much frustrating a release would be for you.
At our company, I faced the same issue and came up with a script that solves this problem, let’s breakdown the problem statement and then solve the atomic sub-problems.
Problem 1: What shall be my folder structure?
I have created 3 folders ‘Build’, ‘BuildSim’, and ‘BuildUni’, the names are self-explanatory where Build is for iphoneos frameworks. I have also kept the script file at the same level.
Problem 2: How do I build the frameworks from command line?
First we define some config variables that we can change as per our build’s requirement.
One issue with my project is, it has to be compiled in a certain order. So. we’ve created different methods to handle this.
We basically make use of the xcodebuild command and specify our parameters to build the project.
One issue that I found, while building for iphonesimulator from command line: It was not generating the executable, neither the *-swift.h headers were generated for the frameworks. So, before running this script I just run the project on the simulator through Xcode (as I’d also have to verify the project before releasing it).
Problem 3: How do I create universal frameworks?
Once the script generates the frameworks for iphoneos, and as the frameworks are already generated for the simulator. I use the method below, to copy the iphoneos and iphonesimulator frameworks from Derived Data.
Let’s now do some magic 🤠
First we copy all the iphoneos frameworks from Build directory to the BuildUni directory, this gives us the executable, headers, and swift modules of the iphoneos frameworks.
Second, we copy all the swift modules from the simulator frameworks in BuildSim folder to the swiftmodules inside the frameworks in BuildUni folder. Now all our frameworks inside BuildUni folder has the swift modules of both iphoneos and iphonesimulator. Neat! 😃
Third, we make use of lipo command to create the fat binaries, it basically combines all the frameworks present in the two input binaries and saves it as one binary at the output location. Almost done! 🍭
Now we need to edit the headers:
The header for our frameworks inside BuildUni currently looks like this, it has #if 0 and then starts with #elif (same as of the iphoneos framework), we basically need to change the #if 0 with #if TARGET_OS_SIMULATOR and inside this if condition we need to paste all the content of the header of the iphonesimulator framework.
Now this is our final nail in the coffin!
First, we remove the 1st line from the universal framework’s header (the #if 0 line)
Now, we create an intermediate file to handle the operations, as the output redirection operator (>>) appends the contents.
In this blank file, we first insert #if TARGET_OS_SIMULATOR
After that, we append all the content of the header from the simulator framework to the intermediate file.
Now, we append the content of the header from the universal framework (Remember, we had removed the first line from this) to the intermediate file.
Almost done, now just some cleanup. We now delete the header file inside universal framework, then move the intermedate file inside the universal framework’s header and rename it to be of the same name as the framework. After that we delete backup files that were created with sed. As, the sed command for BSD based OS (sed -i ‘.bak’ ‘1d’ “file”), is slightly different than Linux (sed -i ‘1d’ “file”) and requires to specify a backup extension.
Now you have iphoneos, iphonesimulator and universal frameworks, all created in less than 30mins (for my workspace of 14 projects), which with manual effort used to easily take at least 4 hours!
The complete script: https://snippet.host/pprs