Autogenerate LinuxMain.swift in git commit hook

Dealing with linux tests in SPM packages can be annoying, because any additon to your tests or a change to a test name requires you to manually update LinuxMain.swift and/or the allTests array.

The following short, one-time setup will autogenerate all required stubs whenever you run git commit (in the CLI or via a git GUI) and ensure your Linux tests are always up to date.

 Rejected commit when linux tests are out of date

Rejected commit when linux tests are out of date

  • Add a stencil file template to .sourcery
    curl -s -o .sourcery/LinuxMain.stencil https://gist.githubusercontent.com/finestructure/d8b9098e7ba8ecb505c24db5684c7744/raw/09ce4d93296aa5822dd25ec8ffcb980e0773923c/LinuxMain.stencil
  • Add a helper protocol.
    curl -s -o Tests/LinuxTesting.swift https://gist.githubusercontent.com/finestructure/17d2cd362fd6ba140caa6838b9e3cee5/raw/e76467cc643efca2cdfea30434c8b5e04137e3a5/LinuxTesting.swift
    The stencil file can be set up to automatically add XCTestCase subclasses but this protocol makes it easier to deal with further subclassing.
  • Add a git commit hook

    TESTABLE_IMPORT_NAME=AppTests
    cat <<EOF > .git/hooks/pre-commit
    #!/bin/sh
    sourcery \
      --templates ./.sourcery \
      --sources Tests \
      --args testimports='@testable import '"$TESTABLE_IMPORT_NAME" \
      --output Tests/LinuxMain.swift \
      &>/dev/null
    if [[ -n \$(git diff) ]]; then
      echo "Linux tests were out of date."
      echo "Your files have been updated, please review and add them to the commit."
      exit 1
    fi
    EOF

    TESTABLE_IMPORT_NAME needs to be set to your test module, which is then imported as @testable in LinuxMain.swift. Usually this is called <YourTarget>Tests.

And that's it. With these things set up, git commit will have your back and alert you whenever your linux tests are out of sync.

Alternative: Xcode "Run Script" build phase

Instead of setting up a commit hook, the call to run sourcery can also be triggered from an Xcode "Run Script" build phase.

While this has the nice advantage of making sure your files are updated sooner in the development cycle and not just when you are getting ready to commit a change, I've found this comes with a couple of drawbacks:

  • does not work with other code editors
  • swift package generate-xcodeproj (or tools that make use of it, like for instance vapor update) will likely overwrite your changes to the project file

I tried using a script phase for a while but eventually got tired of having it overwritten all the time.