Automatically merging conflicts in Xcode project files

Dealing with code conflicts is somethings every developer is probably used to, but it is never a good experience, especially when dealing with file formats that are not exactly human readable, like Xcode project files.

The Xcode projects files (project.pbxproj) are a proper mess with every file appearing multiple times, being referenced by an id, etc .. definitely not something you would want to deal with manually. Luckily there is a better way.

Kintsugi

Kintsugi is lightweight tool to automatically resolve Git conflicts that occur in Xcode project files. You install the Kintsugi gem with

gem install kintsugi

and then simply calling it a path to a project as a parameter.

For example if you have a merge conflicts in App/project.pbxproj you execute

kintsugi App/project.pbxproj

and Kintsugi will take care of the conflicts for you.

I have been using Kintsugi for a few weeks now and I have to say it works really well. There was only one time so far it failed to automatically merge a conflicts I had in an Xcode project file.

Git  Xcode  iOS 

Allowing parallel iOS UI tests runs in CI

If you have your CI machine set up to run multiple jobs in parallel you might have encountered a problem. You cannot run multiple iOS UI tests in the same simulator at the same time. They will fail.

The problem

Imagine this scenario. You have one CI machine that allows two or more jobs to run in parallel. You have a UI tests job set up. Two of your developers push changes to their branches at (almost) the same time. The CI machine then tries to run two UI tests at the same time in the same simulator and at least one of them fails and has to be retried.

You can of course solve this problem by not allowing parallel jobs on the CI machine, but that slows down the CI process. There is a better way.

The solution

You can create a brand new iOS simulator instance for every job, run the UI tests in this new simulator instance and then delete it when you are done. This way no matter how many iOS UI tests jobs you run in parallel they will all use a separate iOS simulator instance and not affect each other.

Creating a new iOS simulator instance

The first step is to choose a unique simulator name for each job. You can generate some random guid for example. In Gitlab CI I just use the ID of the Gitlab CI job that is stored in the ${CI_PIPELINE_ID} environment variable

First you need to create the new iOS simulator instance at the start of the CI job

xcrun simctl create ${CI_PIPELINE_ID} com.apple.CoreSimulator.SimDeviceType.iPhone-11 `xcrun simctl list runtimes | grep iOS | awk '{print $NF}'`

This command creates a new iOS simulator instance with the given name using the iPhone 11 device and the latest simulator runtime. Using the latest simulator runtime makes sure that when you update Xcode on the CI machine you do not have to do any changes to you CI scripts.

Using the new iOS simulator instance

Now that you have a new iOS simulator instance created you can use it to run your iOS UI tests. You can simply pass the name to the xcodebuild command if you use it directly or use it instead of the device type when using Fastlane

[Read More]
CI  Xcode  iOS 

Reading environment variables from iOS and macOS unit tests

In some environments like the CI/CD you might need to read environments variables from your iOS or macOS unit tests.

A typical example might be reading secrets or configuration that is CI/CD specific and you do not want to store it in source control.

In Swift you can read environment variables using the ProcessInfo.processInfo.environment dictionary, for example

let host = ProcessInfo.processInfo.environment["apiHost"] ?? "default fallback host"

The problem is this code does not just work, there is one other step you need to do first.

You need to edit your Test scheme and add every environment variable you want to read to the Environment Variables section.

You can rename the variables but for simplicity and readability you should probably keep the same names.

Swift  Xcode 

Graying out images in Cocoa

I have been working on a macOS application recently where I encountered an interesting task to grey out flags for countries that are disabled.

I first tried overlaying a semi-transparent gray view (fun fact, you cannot directly set a background for a view in Cocoa, you need to use the layer) over the flag image but it did not look very good.

Luckily there is a way to convert a NSImage to grayscale directly in Cocoa.

You first need to create a bitmap representation your NSImage

let bitmap = NSBitmapImageRep(cgImage: cgImage)

convert it to grayscale

let grayscale = bitmap.converting(to: .genericGray, renderingIntent: .default)

and then construct an NSImage from the result.

let grayImage = NSImage(size: grayscale.size)
greyImage.addRepresentation(grayscale)

Putting it all together as an NSImage extension might look like this

[Read More]
Swift  Xcode  Cocoa 

Using different Git config for personal and work projects

I use the same machines to work on both personal and work projects. I usually have to use a different Git identity for the work projects than for my personal projects.

Previously I had my personal Git identity set globally and then used local Git configs to override it in work projects. This worked just fine but it was too much work. There is a better solution.

Git config allows you to use, or better to say include, another Git config for a specific directory and all its subdirectories. I have all my projects stored in ~/Projects and subdirectories like ~/Projects/open-source and work projects in ~/Projects/CompanyName.

I created a ~/.companyName.gitconfig that overrides just the name, email and GPG signing key to match the work identity

[user]
    name = Igor Kulman
    email = igor@company.name
    signingkey = ABC

I then included this config in my main ~/.gitconfig just for the ~/Projects/CompanyName directory

[user]
	name = Igor Kulman
	email = igor@kulman.sk
    signingkey = DEF
...
[includeIf "gitdir:~/Projects/CompanyName/"]
    path = ~/.companyName.gitconfig

to achieve exactly what I needed.

To verify and quickly check which Git identity is being used in a specific Git repository you can use this simple Git alias

[alias]    
    whoami = "! git var -l | grep '^GIT_.*_IDENT'"
macOS  Xcode