Paul Calnan's Blog
Published May 13, 2020

Computing cryptographic hashes in Swift has gotten easier in recent times. Prior to Xcode 10, it used to be difficult to import the CommonCrypto library which did the computation. Xcode 10 added a module for that library, making it easier to do the import. But the API still looks like a C API. Using the library directly requires you to write something like this:

import CommonCrypto
import Foundation

// Compute the digest of this string:
let messageString = "The quick brown fox jumps over the lazy dog"

// Convert the string to a byte array.
let messageData = Data(messageString.utf8)

// Compute the hash using CommonCrypto.
var digestData = Data(count: Int(CC_SHA224_DIGEST_LENGTH))
digestData.withUnsafeMutableBytes { (digestBuffer: UnsafeMutableRawBufferPointer) -> Void in
    messageData.withUnsafeBytes { (messageBuffer: UnsafeRawBufferPointer) -> Void in
        _ = CC_SHA224(messageBuffer.baseAddress,
                      CC_LONG(messageBuffer.count),
                      digestBuffer.baseAddress?.assumingMemoryBound(to: UInt8.self))
    }
}

// Convert the digestData byte buffer into a hex string.
let digestString = digestData.map { String(format: "%02hhx", $0) }.joined()

// prints 730e109bd7a8a32b1cb9d9a09aa2325d2430587ddbc0c38bad911525
print(digestString)

That's a fair amount of boilerplate to accomplish this. Also, the meat of that code — the withUnsafeMutableBytes and withUnsafeBytes — changed between Swift versions.

Rather than deal with that in production code, I wrote a small Swift library called HashAlgorithm to simplify the process.

Using it is pretty simple. Instead of the code above, you just do this:

import Foundation
import HashAlgorithm

// Compute the digest of this string:
let messageString = "The quick brown fox jumps over the lazy dog"
let digest = HashAlgorithm.SHA224.digest(messageString)

// prints 730e109bd7a8a32b1cb9d9a09aa2325d2430587ddbc0c38bad911525
print(digest)

It supports all of the (non-deprecated) cryptographic hash algorithms currently provided by CommonCrypto: SHA1, SHA224, SHA256, SHA384, and SHA512.

The library is available on Github.

Published May 13, 2020

Has Xcode stopped working for you? Builds acting funny? Autocomplete stopped working? You've quit and restarted it, cleaned the build folder, and it's still broken?

Try running this.

#!/usr/bin/env bash

set -euo pipefail

echo "Exiting Xcode"
osascript -e 'quit app "Xcode"'

echo "Deleting DerivedData"
rm -rf ~/Library/Developer/Xcode/DerivedData/*

echo "Deleting llvm module cache"
rm -rf "$(getconf DARWIN_USER_CACHE_DIR)/org.llvm.clang/ModuleCache"

echo "Relaunching Xcode"
open -a Xcode
Published May 10, 2020

I'm working on a side project that has required some on-device troubleshooting. I find myself loading a build on my phone and using it for several days. There's been a lot of trial-and-error debugging in this project, so I often run code from an experimental branch for extended periods of time. It's easy to lose track of what source configuration I have loaded on my phone at any given time.

I wanted to get a way to get the current Git revision and branch into the app. I figured a good start would be the following:

I wanted to get this information into each build, not just release builds. Getting this information from a shell script into a build is tricky. One of the problems is that you can't modify the working copy. Doing so would change the "is clean" flag. The build would always show dirty and a commit would be needed to clean it, changing the commit hash.

The approach I settled on was to write an untracked Swift file as part of the build process.

First, add generate-git-revision.sh (see below) to your project's bin directory (or wherever you keep your project's scripts).

#!/usr/bin/env bash

set -euo pipefail

cat <<SWIFT
//
//  GitRevision.swift
//
//  This file was automatically generated. Do not edit manually. It will be overwritten on every build.
//
//  IMPORTANT: This file must be added to .gitignore or it will dirty the working tree every time there is a build.
//

/// Information about the current Git revision.
struct GitRevision {

    /// The name of the current branch.
    static let branch = "$(git rev-parse --abbrev-ref HEAD)"

    /// The abbreviated hash of the current commit.
    static let commit = "$(git rev-parse --short HEAD)"

    /// Whether the working tree is clean. If false, indicates that there are uncommitted or untracked files in the working tree.
    static let clean = $([[ -z $(git status -s) ]] && echo 'true' || echo 'false')

    /// Build timestamp. Set when this file was generated at the beginning of the build process.
    static let timestamp = "$(date +%Y-%m-%dT%H:%M:%S)"

    /// A string summarizing the current Git revision.
    static var description: String {
        return "\(commit)\(clean ? "" : "-dirty") \(branch) \(timestamp)"
    }
}
SWIFT

Next, in Xcode add a new Run Script build phase at the top of the list of Build Phases (directly under Dependencies). Call the new build phase Generate GitRevision.swift. Make the body of the build phase script:

"${PROJECT_DIR}/path/to/generate-git-revision.sh" > "${PROJECT_DIR}/path/to/GitRevision.swift"

Build the project. This causes the script to run and generate GitRevision.swift.

Now, add the new GitRevision.swift file to the project. Also add GitRevision.swift to the project's .gitignore file.

That's it. The generated GitRevision.swift file will look something like this:

//
//  GitRevision.swift
//
//  This file was automatically generated. Do not edit manually. It will be overwritten on every build.
//
//  IMPORTANT: This file must be added to .gitignore or it will dirty the working tree every time there is a build.
//

/// Information about the current Git revision.
struct GitRevision {

    /// The name of the current branch.
    static let branch = "development"

    /// The abbreviated hash of the current commit.
    static let commit = "3c747d5"

    /// Whether the working tree is clean. If false, indicates that there are uncommitted or untracked files in the working tree.
    static let clean = false

    /// Build timestamp. Set when this file was generated at the beginning of the build process.
    static let timestamp = "2020-05-10T23:03:15"

    /// A string summarizing the current Git revision.
    static var description: String {
        return "\(commit)\(clean ? "" : "-dirty") \(branch) \(timestamp)"
    }
}

If you leave that file open in Xcode when you build, you can see the property values change as the file gets regenerated.

You can now reference GitRevision.description from your app's code to show the commit, branch, and build time.

Published May 10, 2020

Here's a simple Python script to pretty-print a JSON file:

#!/usr/bin/env python

from __future__ import print_function
import sys
import json

try:
    json.dump(json.load(sys.stdin), sys.stdout, sort_keys=True, indent=4)
except ValueError as err:
    print("Error:", err, file=sys.stderr)
    exit(1)

To format JSON from the pasteboard:

$ pbpaste | ppjson

To format JSON in a file:

$ ppjson < filename

Both with print the formatted JSON to stdout. To write to a file, you can redirect the output like this:

$ ppjson < input_filename > output_filename

Or, you could format JSON from the pasteboard (using pbpaste piped through ppjson) the copy the formatted JSON back to the pasteboard (using pbcopy):

$ pbpaste | ppjson | pbcopy
Published May 10, 2020

I can never remember the commands to delete a Git tag. It's one of those things I don't do frequently enough to remember — I always have to look it up.

To delete a local tag called TAGNAME:

$ git tag --delete TAGNAME

To delete a remote tag called TAGNAME:

$ git push --delete origin TAGNAME

But rather than have to enter in two commands, I wrote a script that combines them:

#!/usr/bin/env bash

set -euo pipefail

E_BADARGS=85
if [ $# -ne 1 ]; then
    echo "Usage: $(basename $0) tag-name"
    exit $E_BADARGS
fi

tagname="$1"

# Delete the local tag
echo "Deleting local tag $tagname"
git tag --delete $tagname

# Delete the remote tag
echo "Deleting remote tag $tagname"
git push --delete origin $tagname

That script is saved in my ~/bin directory under the name git-rmtag. Using that naming convention, you can run it like this:

$ git rmtag TAGNAME

and it will delete the local and remote tags.

Older Posts