Seamless Transition: Migrating Tuist from v3.0 to v4.0

iOS Aug 23, 2024

At Halodoc, we continually seek to advance our tech stack and benefit from the latest improvements and innovations. Recognising the significant advantages of Tuist v4, we had been planning to transition from Tuist v3 for some time. We were eager to embark on this migration to capitalise on the enhanced features and capabilities that Tuist v4 offers.

What are SwiftPM & Tuist

Swift Package Manager (SwiftPM) is a built-in tool for managing dependencies in Swift projects, streamlining the process of downloading, compiling, and linking libraries. Tuist, on the other hand, is a powerful open-source tool that aids in generating and maintaining Xcode projects and workspaces, making complex project setups easier to manage. In this blog, we’ll explore how to smoothly transition from Tuist v3.0 to v4.0, leveraging new features and improvements to enhance your development workflow.

Existing Implementation

Before diving into the migration process, it's important to understand the current setup. Our project is currently utilising Tuist v3.0, which has served us well by automating the generation of Xcode projects and workspaces. This setup has streamlined our development process by reducing the manual effort required to manage complex project structures. However, as the project has grown, we have encountered some limitations in build times and dependency management. By upgrading to Tuist v4.0, we aim to address these issues and take advantage of the latest features and optimisations.

Why Migrate?

Tuist v4.0 offers enhanced build system efficiency and improved dependency management, significantly reducing build times and simplifying third-party library integrations. Additionally, it ensures compatibility with the latest Swift and iOS versions, future-proofing development infrastructure.

Steps involved in Migrating from Tuist v3 to Tuist v4

  1. Key Changes and Enhancements in Tuist v4
  2. Adopt Package.swift as the DSL for dependencies
  3. Dropped Carthage integration via Dependencies.swift
  4. Enabling Tuist Cache to improve the build time
  5. Adopting MAKE_MERGEABLE option to improve the app launch time on release builds

1. Key Changes and Enhancements in Tuist v4.18.0

  • Streamlining Tooling with Mise
    Tuist has dropped version management through tuistenv in favor of Mise, a more flexible tool that ensures consistent version management across different tools. To switch to Mise, we uninstall tuistenv and install tuist v4 via mise.
  • Uninstall tuist.  curl -Ls https://uninstall.tuist.io | bash

Tuist Installation via mise

  • Simplifying Project Models
    To enhance API readability and expressiveness, Tuist has removed init constructors from all ProjectDescription models, replacing them with static constructors named after the models themselves. we incorporated the changes for targets, projects and paths.
  • In earlier versions of Tuist, the deployment target and platform were specified using the deploymentTarget and platform properties. This required explicitly defining the target version, devices, and platform.
  • With the release of Tuist v4, the configuration has been simplified to improve readability and ease of use. The deploymentTargets property is now used, which eliminates the need to specify the devices explicitly, assuming the default to be both iPhone and iPad. Additionally, the platform property has been replaced with destinations, allowing for more flexible and precise configuration.
  • Aligning with Industry Standards
    In alignment with industry conventions, the tuist fetch command is now tuist install. We Updated our projects makefile accordingly, to reflect this change.

2. Adopt Package.swift as the DSL for dependencies

With Tuist v4, we are transitioning to using Package.swift as the Domain-Specific Language (DSL) for managing dependencies. This approach leverages Swift's native package management capabilities, offering a more streamlined and integrated way to handle project dependencies. By adopting Package.swift, we can define dependencies in a Swift-native syntax, enhancing compatibility and simplifying the overall setup.

This change also enhances support for tools like Dependabot or Renovatebot, enabling them to automatically update our dependencies. This automation streamlines the update process, ensuring that we are always working with the latest and most secure versions of our dependencies.

Package.swift

3. Dropped Carthage integration via Dependencies.swift

Why We use both Carthage and SPM Dependencies

Carthage has been a go-to choice for several reasons, especially in cases where Swift Package Manager (SPM) fell short. Here are the primary reasons we chose Carthage:

  1. Lack of SPM Support for Some Dependencies: One of the main reasons for using Carthage was that few minor dependencies in our project did not support SPM. Carthage allowed us to integrate these essential libraries seamlessly, ensuring that our project could leverage their functionalities without compatibility issues.
  2. Binary Framework Support: Carthage provides the capability to integrate pre-built binary frameworks, which is particularly beneficial for dependencies that are time-consuming to compile. This feature helps reduce overall build times by using pre-compiled binaries, a feature that was not available with SPM at the time.

As part of the migration from Tuist v3 to v4, a notable change is the removal of Carthage integration through Dependencies.swift.

As we were using Carthage dependencies, we have to use Carthage directly to pull the pre-compiled frameworks and XCFrameworks into Carthage's standard directory, and then reference those binaries from the targets using the TargetDependency.xcframework and TargetDependency.framework cases.

To generate the workspace we are installing the Carthage dependencies on the fly first and then installing the SPM and CocoaPods dependencies.

Here are the makefile changes, where carthageUpdate corresponds to installing Carthage dependencies

4. Enabling Tuist Cache to improve the build time

As part of the migration process from Tuist v3 to v4, optimising build times is crucial. One effective way to achieve this is by enabling Tuist Cache. This caching mechanism helps store and reuse build artefacts, significantly reducing the time required for subsequent builds.

Note: Tuist has renamed the tuist cache warm command to tuist cache for simplicity.

Adoption of Tuist Cache in Local Workspace Generation

In our development environment, we make use of  the command tuist Cache to cache artefacts for both simulator and the device. This local caching ensures that developers can quickly rebuild their projects without waiting for unnecessary recompilation of unchanged targets.

Adoption of Tuist Cache in Jenkins Workspace Generation

We have streamlined our CI/CD pipeline by integrating Tuist Cache in our Jenkins setup. A dedicated job caches the binaries and stores them in a common, accessible location. For every build, we provide options to either use the cached artefacts or perform a fresh build.

  • Using Cached Binaries: If developers choose to use the cached binaries, the build process will refer to the shared resource where the binaries are stored. By using precompiled binaries during compile time, saves significant time and resources.
  • Fresh Build Option: Developers still have the flexibility to perform a fresh build if needed, ensuring that the cache does not hinder any specific requirements or preferences.

Here are the makefile changes

We initially cached the frameworks using tuist cache and stored it in a common place.

  • Using Cached Binaries: To generate a workspace using cached binaries, we use the generateWorkspaceWithCache command. During the generation stage, the subcommand generate checks for the availability of cached binaries from the specified configuration (which is  stageRelease here) and uses them to speed up the process.
  • Without using Cached Binaries - In some cases, we may need to generate a workspace without using the cached binaries. For example when we need to upgrade or add certain dependencies. For such scenario, we use the generateWorkspaceWithoutCache command. During the generation stage, the subcommand generateWithoutCache employs the --no-binary-cache option to bypass the use of any cached binaries.

5. Adopting MAKE_MERGEABLE option to improve the app launch time on release builds

As part of migrating from Tuist v3 to v4, we took advantage of new build options such as MARK_MERGEABLE. This option allows you to mark certain targets or dependencies as mergeable, potentially reducing the overall size of the final binary and improving build times.

The MARK_MERGEABLE option in Xcode allows developers to specify that certain targets or dependencies should be merged into the main binary rather than being included as separate dynamic libraries.

How to Add MARK_MERGEABLE in Tuist

  1. Update Your Tuist Project Configuration: To include the MARK_MERGEABLE option, you’ll need to update your Project.swift configuration in Tuist.

Here is an example of how to set this option:

To automatically merge libraries, first open the Tuist project in Xcode 15 or later. Then, add the MERGED_BINARY_TYPE build setting to your app target, and set the value to automatic. With this build setting, Xcode treats mergeable dependencies like normal dynamic libraries in debug builds, but performs steps in release mode to automatically handle merging for direct dependencies

In some cases, you may want to manually configure merging between your app or framework target and dependent libraries. For example, you might not want to automatically merge dependencies that you share between an app and an app extension if you’re concerned about the app extension’s binary size. To set up manual merging, configure your app or framework target, then configure your dependent libraries. In your app or framework target, add the build setting MERGED_BINARY_TYPE and set it to manual

Project.swift

2. Generate Your Project: After updating your Project.swift file, run tuist generate to regenerate your Xcode project with the new settings.

3. Verify the Configuration: Open your Xcode project and verify that the MERGED_BINARY_TYPE setting is correctly applied to the desired targets.

Benefits of Using Tuist Cache and Mergeable Libraries in Our Project

Why Use MARK_MERGEABLE?

  1. Reduced Linking Time: With fewer dynamic libraries to link, the overall linking process becomes faster. Instead of linking multiple dynamic libraries, the compiler only needs to link a single, merged binary.
  2. Decreased Metadata Generation: Generating metadata for each dynamic library adds overhead. By merging these libraries, the need for separate metadata generation is eliminated, further speeding up the build process.
  3. Simplified Dependency Management: Managing fewer dynamic libraries reduces the complexity of the build process, minimising the chances of dependency-related issues that can slow down the build.

Why Use Tuist Cache?

  1. Faster Build Times: By reusing cached build artefacts, you can significantly reduce the time required for compiling dependencies.
  2. Reduced CI/CD Times: Caching helps speed up continuous integration and deployment processes, leading to quicker feedback and deployments.
  3. Consistency Across Builds: Cached artefacts ensure that builds are consistent across different environments and developers.

Key Optimisations and Improvements

  1. Build Time Optimisation: Utilising mergeable libraries has reduced the build time for debug builds by approximately 30 seconds for cold compile. This improvement speeds up the development process, allowing developers to iterate and test changes more efficiently.
  2. Enhanced Launch Time: Mergeable libraries have also contributed to reducing the launch time of release builds by ~1.5 seconds. This enhancement ensures a quicker start for users, improving overall app performance and user satisfaction.
  3. Efficient Project Build: Tuist caching pre-compiles third-party libraries, further reducing the project build time. This efficiency gain is particularly beneficial in large projects with multiple dependencies, as it minimises the time spent on compiling unchanged libraries.
  4. CI/CD Pipeline Improvements: Tuist caching has led to a notable reduction in continuous CI/CD operation time in Jenkins. The archiving artefacts stage run time has been reduced from 5 minutes to 3 minutes. This improvement is crucial for maintaining a fast and reliable development workflow, enabling quicker deployments and more frequent updates.

References

Join us

Scalability, reliability and maintainability are the three pillars that govern what we build at Halodoc Tech. We are actively looking for engineers at all levels and if solving hard problems with challenging requirements is your forte, please reach out to us with your resumé at careers.india@halodoc.com

About Halodoc

Halodoc is the number 1 all around Healthcare application in Indonesia. Our mission is to simplify and bring quality healthcare across Indonesia, from Sabang to Merauke. We connect 20,000+ doctors with patients in need through our Tele-consultation service. We partner with 3500+ pharmacies in 100+ cities to bring medicine to your doorstep. We've also partnered with Indonesia's largest lab provider to provide lab home services, and to top it off we have recently launched a premium appointment service that partners with 500+ hospitals that allow patients to book a doctor appointment inside our application. We are extremely fortunate to be trusted by our investors, such as the Bill & Melinda Gates Foundation, Singtel, UOB Ventures, Allianz, GoJek, Astra, Temasek, and many more. We recently closed our Series D round and in total have raised around USD$100+ million for our mission. Our team works tirelessly to make sure that we create the best healthcare solution personalised for all of our patient's needs, and are continuously on a path to simplify healthcare for Indonesia.

Download Halodoc app via iOS and Android.

Varun M

iOS devotee with a flair for Android—because a true developer's heart knows no bounds!