Log in
learn

Dynamically generating Xcode projects

In this blog post I talk about how Xcode's determinism and speed relates to the static nature of Xcode projects, and how Tuist leverages dynamism to help teams overcome the challenges of scaling up projects.

I’ve been thinking a lot lately about the static nature of Xcode projects. I think it’s a design decision that makes Xcode behave fast and deterministically, at least if the project is reasonably small. In Android land, things are dynamic. Gradle has a loading phase where all the tasks are evaluated, and only then can you build and run your apps.

Personally, I think the dynamic approach at scale is terrible. If the project is considerably large, it might take long and a lot of CPU power for Gradle to become responsive. The matter gets worse if we use tasks with side effects that might cause Gradle to yield different results in different environments.

For that reason, if I were Apple, I would keep Xcode projects as static as possible. With Xcode 11, they added support for the Swift Package Manager, which, as you might have noticed, added a bit of dynamism to resolve the packages at launch time.

Apple could introduce more subtle touches of dynamism to ease the maintenance of projects and minimize the likelihood of getting Git conflicts. For example, the files that are part of a project could be defined using glob patterns like Sources/**.swift, or even better, following a convention for the directory structure like Android does. They could be resolved at launch time like they are currently doing with Swift packages.

At this point, you may wonder how Tuist relates to this dynamism I'm talking about: Tuist brings dynamism through project generation to help teams overcome the challenges of scaling up projects.

Here are some examples of things that we were able to provide by leveraging the dynamism of project generation:

And we are not done yet. We are streamlining more workflows like configuring the environment and the projects for signing and providing a standard CLI without having to mess around with script files or configure a Ruby environment properly.

Like Rails does in the Ruby community, we are designing Tuist to spark joy when interacting with Xcode projects. Over the past years, we have seen a good amount of tools to help teams with all the different challenges that they face. That's awesome, but when combined together around Xcode projects, teams end up with workflows that don't play well with each other and layers of indirection that add complexity and make the projects hard to reason about.

Here are some concrete examples to illustrate the above:

  • Inconsistent Swiftlint errors because Homebrew installed different versions of Swiftlint.
  • Fastlane lanes printing errors but exiting with a exit status 0.
  • Xcode failing to sign the app because a developer changed one of the signing build settings by mistake and no one spot it in a large project.pbxproj diff.
  • Invalid dependencies configuration that results in some artifacts being copied into the final product and then causing release validations to fail.
  • Developers getting different results when running Fastlane because they are not familiar with setting up Ruby environments and end up running the global Fastlane instead of the one pinned by Bundler.
  • Adding a new framework to the dependency tree causes compilation issues on CI.

Some teams might want to work in such a frustration-prone and complex environment. It gives teams the freedom to customize every single bit of the way they work. Still, it also leads them to waste time that they could otherwise spend building product features. Tuist takes care of those things, ensuring that we provide a consistent and easy experience that sparks joy.

If you have been there and think it’s time to clean things up and make working with your project as enjoyable as it used to be when you first created the project with Xcode, you can check out our Get Started documentation.

Supercharge your Swift app development

Get started

You might also like

Vibe Xcoding your apps
Explore how LLMs are changing the way we code and the exciting opportunities ahead as Apple brings 'vibe coding' to the Xcode ecosystem for Swift developers.
Define your watchOS apps and extensions easily with Tuist 0.19.0
Until today, defining watchOS apps and extensions in Tuist was not possible. The good news is that from Tuist 0.19.0 that's no longer true because it extends its beautifully simplified abstractions to watchOS. On top of that, we also shipped support for enabling test coverage in the schemes, and defining the deployment targets in targets. We also took the opportunity to iron out some bugs that had been reported by users.
Visualize your projects graph from Tuist 0.17.0
One of the difficulties of scaling up Xcode projects comes from the fact that Xcode doesn't provide a high-level picture of the structure of the project. Tuist 0.17.0 fixes that by providing a new command, 'tuist graph', that exports a graph of the project to help users of the tool visualize their project dependencies. This version also adds support for configuring Tuist globally, and also indicate the version of Xcode that is required to run the project.