📜 ⬆️ ⬇️

Overclocking Swift build project in Xcode

image
An article on how to fix Xcode incremental compilation for Swift projects and speed up the build phases for Cocoapods and Carthage without breaking anything.


A small spoiler: on three different projects it was possible to reduce the incremental build time by 9 times!


Tutorial is purely practical with a minimum of water. Be sure to read for existing iOS developers.



Problem


We have several parallel Swift projects in the company and, practically, everywhere there was difficulty with sequential assembly. For each repeated 'Run' took from 30 to 50 seconds. Minute code is written, wait half a minute until the project is assembled. You have time to go for tea, smoke, count taxes and sometimes take a nap.


Definitely had to change something. In my previous articles, where the actual problems of the compiler are described and how to solve them, we were able to achieve some success. However, this did not eliminate the difficulties with incremental compilation, which ate most of the time, constantly knocking out its slowness from the workflow. I am sure everyone faces this regularly, taking it as part of the workflow.


But we didn’t put up with it, having decided to investigate all the circumstances of the case and in the end got a good result. What I will be glad to tell you.


PS The picture in the cap does not mean that we 'skewed' Xcode. This is his supposedly accelerating.


Incremental compilation


If someone is not familiar with the term, this is a way to build only changed places of the code without total recompilation of the entire project. And this is something that doesn’t normally work in Xcode.


In the last post, Comrade Gxost suggested an idea that solved the problem for a while. We have already begun to open the champagne with the whole team and hang a portrait the emperor deliverer on the wall, but unfortunately, at this moment the problem resumed.


Apparently, we are not alone. Recurrences are also written on StackOverflow under the answer, as well as in the original thread on the Apple forums.


This is by no means a stick in the vegetable garden. Quite the contrary - thanks, it all pushed to continue to dig. If even temporarily, but the problem was solved, then the truth is somewhere nearby.


What exactly is this header map for which we set the flag on the recommendation of Apple?
Some web archeology on apple documentation:


Header maps (also known as “header maps”) are files used in a target.

In its own words, header maps are an Xcode index file with project header locations. File about all the headers that we use.


Since I promised not to dive too deeply into theory, here’s an excellent article on this topic. Easy to read, highly recommended.


The main thing is that something in these header maps (or what uses them) provokes the unfair work of incremental compilation. If we have found such a technical tumor, then let's proceed without half measures and simply turn them off.


Go to the build settings and remove extra parts set the existing USE_HEADER_MAPS flag to NO :


image


Now we need to compensate for the lost functionality and manually add the location of all project headers.


Nowhere do we leave the settings and with our hands we prescribe paths to folders with headers in the field 'user header search paths':


image


In the Swift project, they should be small, only your custom Obj-C things.


Again we do a full clean and try to start a project. If you flew into the following error


image


it means you just forgot to mention the path to one or several header files. After correcting them, everything should come together without complications, since apart from that we did not make any changes.


As a bonus, we note that incremental compilation works best with the whole-module-optimization (WMO) turned off, which we talked about last time . This does not mean that the solution does not work for a full-modular optimization, it just goes without a few seconds faster. Let the assembly with a clean slate and stretch for ages.
Here everyone decides for himself that he is more comfortable, faster and better suited. If you decide to opt out of WMO, then it is enough to remove the SWIFT_WHOLE_MODULE_OPTIMIZATION flag from the project settings; there should be no difficulty whatsoever.


Result : we successfully fixed the incremental compilation. From now on, no more than a few seconds should be spent on the very fact of Swift build, not counting codesign, linking and various build scripts (we'll talk about them below). Inside the company, we tested this method on three different projects, several versions of OSX and in general many configurations in general, which also applies to Xcode. And now, for a long time, we do not observe relapses.


Overclocking Cocoapods and Carthage


Probably, many have noticed that, apart from the compilation itself, a lot of time is spent on different 'shell scripts':


image


When using cocoapods and / or carthage there are even several of them. Their execution takes from 3 to 10 seconds each time depending on the speed of your disk, processor and the position of the stars in the sky. Cocoapods registers itself there automatically, and for Carthage you have to do it manually .
Having studied the content a bit, we found out that these scripts do nothing more than copying their resources into the project's build directory. However, they do not care about whether these files were already copied or not. Although it would be much more logical to check the availability of the necessary resources before copying them again.


What we will do.


Let's start with Carthage.


The inspiration for this decision was an underestimated response to StackOverflow , where a disposable crutch was adopted and covered with huskies. By the way, if you suddenly decide to use an approved solution, you will catch wild and hot mistakes in Runtime if you make a full clean.


We will not do this, so let's go the right way and modify our Copy Carthage Frameworks build phase:


FILE="${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Carthage-Installation-Flag" if [ -f "$FILE" ]; then exit 0 fi touch "$FILE" /usr/local/bin/carthage copy-frameworks 

This script additionally copies together with the resources one more empty file, which serves as a beacon that the operation was successful. It does not take place, does not give side effects. and allowed for children from 3 years . The next time the script is launched, it will first check for the presence of this file, and if there is a file, the operation will automatically complete without unnecessary gestures.


To be sure, a screenshot of the result that we should get:


image


Important! If you decide to add or remove a dependency from Carthage, then before assembling the project you should definitely perform a full clean. Otherwise, the script will assume that it has already installed everything for a long time. And you will be trying to find an explanation for what is happening.


By the way, you can find the clean function in the menu along the path Product → Clean.


image


If you also hold down the option (alt), then you can make a special clean, which also expels demons from project removes various local settings, cache, and often failing other nonsense.


Now cocoapods

For beans, the principle is the same, but since the scripts are automatically generated, you have to add a little magic to the Podfile. To do this, we do the following lines at the very end of the file:


 post_install do |installer| Dir.glob(installer.sandbox.target_support_files_root + "Pods-*/*.sh").each do |script| flag_name = File.basename(script, ".sh") + "-Installation-Flag" folder = "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" file = File.join(folder, flag_name) content = File.read(script) content.gsub!(/set -e/, "set -e\nKG_FILE=\"#{file}\"\nif [ -f \"$KG_FILE\" ]; then exit 0; fi\nmkdir -p \"#{folder}\"\ntouch \"$KG_FILE\"") File.write(script, content) end end 

The principle of operation of this script is the same: it adds a check for a file flag to resource copy scripts. It does not change the build phases, unlike Carthage, but immediately modifies the cocoapods script itself.


By the way , if you already had the post_install block involved, then you do not need to create another one, just place the script inside the existing one. The latest versions of Cocoapods (1.1.1) display a warning if you screw up, but earlier ones just silently swallow the error, providing you with a fun debugging.


Now you can make ' pod install ' for the changes to take effect. As in the case of Carthage, when editing a Podfile, you also need a full clean project before launching.


image
PS Ruby is not my native language, hold sneakers.


Conclusion


Thus, we managed to reduce the project build time from 45 seconds to 5 seconds.


I almost forgot proofs. Assembly time to:


image


Screenshot taken from the video to the last article.


Assembly time after:


image


In my opinion, extremely significant result. The cost reduction approach must necessarily be in the arsenal of each company and developer.


I am sure that among you there are people with Xcode optimization experience who are ready to supplement and share their thoughts. And in general, I would like to see questions in the comments so that in my next articles I mention you, your experience, the experience of your colleagues and highlight the topic, since for me it is a hobby, life and work.


Finally, I will share the utility I used to measure the compile time of methods: https://github.com/RobertGummesson/BuildTimeAnalyzer-for-Xcode
Shows individually each function and the total time spent on its assembly.


')

Source: https://habr.com/ru/post/317650/


All Articles