Understanding the Xcode build system - Part 1
Building app is the thing that we do everyday, especially while development. In this article, we will take a closer look at Xcode's build system, what it does automatically for us, and what we can do to make the build process more efficient and reliable. The article is divided into 3 parts:
- Understanding the general build system and some terminologies (part 1)
- Understanding the Xcode build system and Xcode project structure (part 2)
- How can we help the Xcode build system (part 3)
Some terminologies
Software build
According to Wikipedia:
a build is the process of converting source code files into standalone software artifact(s) that can be run on a computer, or the result of doing so.
In our context, a smartphone is also a "computer" as well. As a software engineering, we do "coding" everyday, and the result of this process is to create a program which can run on our device
Framework and Library
We might have heard many times about Framework and Library everyday and they sometimes can be used interchangeably, but they actually have different meanings. The main difference can simply described by this image:
For more understanding, I'd recommend you to read this interesting article.
In iOS development using XCode, those terms mentioned above have slightly more specific meanings.
Library is a collection of object files (.o files) that a program can link to. There are two types of library in iOS: Static library and Dynamic library. Static library as its name stands for, is packed into the main executable and linked at compile time and basically it only contains executable code. Object files in this kind of library are archived into .a file. And since this kind of library is linked at compile time, it will not be reused or shared between programs. To improve the above limitation, we also have Dynamic library which is referenced by the linker at runtime, so it is not copied into a program. And instead of .a file, dynamic libraries are .dylib files.
At WWDC23, Apple has introduced a new generation of Library which inherits from the above ones called Mergeable Library, which makes dynamic libraries become mergeable. It takes advantages from both Static library and Dynamic library. Similar to Dynamic library, it reduces the works of the static linker comparing to Static library (as the merging process will be faster than linking statically), which helps the build become faster too. Moreover, many dynamic libraries are able to be merged into only one, meaning there are less things to do for the dynamic linker, thus improves the app launching as well. It's also able to reduce the size after the merging process.
Framework is the one we might be more familiar with e.g. UIKit, CoreData, ... We use them everyday of iOS development. Unlike libraries, frameworks are structured directories containing code, sub-directories, shared libraries, and other support files e.g. assets files, ... Similar to the above principles, the key different between a library and a framework is that the framework can "call" our code, which can be define as Inversion of Control. And similar to Library, we also have Static framework and Dynamic framework.
Static frameworks are linked during build time and Dynamic frameworks are linked at runtime. The important thing to note that there is different between user-provided dynamic frameworks and system-provided dynamic frameworks where we have to embed the user-provided ones within our app bundle and they can not be updated independently from our app like the system-provided ones. For example, an app built with Swift 5.0 will run on systems that have a Swift 5 standard library installed, as well as those with a Swift 5.1 or Swift 6.
Obviously, the dynamic framework can help reduce the app size as well, but it depends on specific situation. Moreover, both dynamic framework and static framework have trade-offs on performance. The static framework has faster startup but slower build times. While the dynamic framework can be size-efficient as it can avoid library duplications inside the app bundle. In general, it is recommend to use static framework when possible. For more details, I'd like to suggest this article
Xcode build system
What is a build system
A build system is a collection of software tools that are used to facilitate the build process. We might have heard about many build systems before e.g. GNU Make, CMake, Buck, Bazel, Gradle ... and our Xcode build system. With the helps of build systems, our developer life is more easier and thus we can focus on our development. Bazel's documentation has a very straightforward definition about this:
Fundamentally, all build systems have a straightforward purpose: they transform the source code written by engineers into executable binaries that can be read by machines. Build systems aren't just for human-authored code; they also allow machines to create builds automatically, whether for testing or for releases to production. In an organization with thousands of engineers, it's common that most builds are triggered automatically rather than directly by engineers.
Why do we need it?
Obviously, most engineers don't need to care about build system when they are learning to code. We usually starts by using the compiler directly to compile our code e.g. gcc, javac, swiftc, ... and our code just works. However this is only a few files and on bigger projects, things are not just simple like that. We might have to deal with many files, many libraries and frameworks, even with many programming languages and many compilers. The build process now is no longer a single-step process. We now need to care about what is called dependencies, the orders between them to make sure the build process works properly and correctly. Without the help of the build system, this process will be error-prone and things can easily get out of hand.
So how about using shell scripts in order to automatically do the above tasks? Well, you can get what you want by using shell scripts to automate things. However, we still have to face with other problems including the scalability problem as follow:
- Tedious: Shell scripts can become tedious to write and maintain as the project grows in complexity.
- Slow: Shell scripts can be slow, especially if they rebuild dependencies every time they are run.
- Difficult to release: It can be difficult to release a project that is built using shell scripts, as there are many steps involved and it can be easy to make mistakes.
- Vulnerable to disaster: If your hard drive crashes, you may lose all of your build scripts and other important files.
- Difficult to scale: It can be difficult to scale a project that is built using shell scripts, as each developer needs to have their own environment set up.
- Not portable: Shell scripts are specific to the operating system and shell environment that they are written for. This can make it difficult to use them on other systems.
- Not robust: Shell scripts are not as robust as other types of build systems. They are more likely to be affected by errors and unexpected input.
- ...
That's why we not only need a tool, but also a system to avoid any hassle.
SDKs
SDK stands for software development kit. Also known as a devkit, the SDK is a set of software-building tools for a specific platform, including the building blocks, debuggers and, often, a framework or group of code libraries such as a set of routines specific to an operating system (OS). For example, for every official release of Xcode, it is usually included SDKs for platforms such as iOS SDK, watchOS SDK, tvOS SDK and newly visionOS SDK.
Xcode Command Line Tools
This is a package enables UNIX-style development via Terminal by installing command line developer tools, as well as macOS SDK frameworks and headers. Many useful tools are included, such as the Apple LLVM compiler, linker, and Make. If you use Xcode, these tools are also embedded within the Xcode IDE.
Behind the scenes, the Xcode IDE use this tool for the build process. This tool also contains some crucial utilities for software development, not only for softwares running on Apple devices. For example the c++ compiler, gcc, git, etc ... are also included. More details about it are described at here
Xcode build system and other build systems
Xcode
As defined by Apple, the Xcode build system manages the tools that transform your code and resource files into a finished app. It organizes the source code and assets in a structure with many terms we might have been familiar with e.g. Project, Workspace, Target, Asset Catalog, Dependencies, ... through the Xcode IDE. They not only help us increase productivity ourself, but also help us with the maintenance and scalability in the future. We will dive deeper into this later in the next section.
Note that the Xcode IDE is totally different with the Xcode build system as it not only contains the Xcode build system but also other tools for development such as code editor, xib editor, debugger and so on.
The Xcode build system is a very useful tool for our development to make a completed app, but it is not the only way. This interesting Article describes how we can build and install an iOS app into the Simulator and Device as well without using the Xcode IDE at all.
Bazel
Bazel is a Google's open-source build and test tool similar to other build systems that have been mentioned before. It uses a human-readable, high-level build language, in this case it is Starlark - a domain-specific language (DSL). It offers the some advantages such as fast but reliable by using caches and parallelism, multiple platforms (Linux, macOS, Windows), scalable and efficient for huge project and very extensible (we can use it for development not only in iOS but also in other system, with different language and framework by using rule e.g. Android, C / C++, Java, Python, Scala, etc ...) See more details about how to use it especially for iOS development.
Buck2
Buck2 is an upgrade to the original Buck build system ("Buck1") designed by Meta. It is the build system similar to Bazel (distributed compilation with Remote Execution API) and Buck1 but with some improvements such as 2x faster, tracks dependencies with far better accuracy than Buck1, support for ultra-large repositories through filesystem virtualization, ...
There is a fact that those above tools can not replace Xcode, they all use some of the Xcode tools such as Xcode IDE, Xcode Command Line tool for the build process.
In the next part, we will go to more details about the Xcode project structure and how it organizes things.
References
https://www.linkedin.com/pulse/module-package-library-framework-real-life-example-mohammad-ibrahim/ https://medium.com/@m.tabrizi/ios-library-vs-framework-30866c41f100 https://www.emergetools.com/blog/posts/static-vs-dynamic-frameworks-ios-discussion-chat-gpt https://medium.com/swlh/a-brief-introduction-to-build-systems-1e45cb1cf667