Skip to content

Commit

Permalink
Update template project
Browse files Browse the repository at this point in the history
The template now has:
* An AppDelegate
* Localization for English and Dutch
* Default tint color and icon
* Unit tests, UI tests with Salad and a test plan
* GitHub Actions configuration
* Default files for: Info.plist, launch screen storyboard, entitlements
* Rename script
* Linting and formatting configuration
  • Loading branch information
mbernson committed May 10, 2024
1 parent 3f27e5a commit e708234
Show file tree
Hide file tree
Showing 26 changed files with 790 additions and 77 deletions.
39 changes: 39 additions & 0 deletions .github/actions/code-signing-setup/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
name: Set up Apple code signing
description: Installs code signing certificate(s) to a temporary keychain
inputs:
build-certificate-base64:
required: true
description: The base64-encoded p12 build certificate
p12-password:
required: true
description: The password for the p12 build certificate
keychain-password:
required: true
description: The password for the temporary keychain
runs:
using: "composite"
steps:
- name: Install code signing certificate
env:
BUILD_CERTIFICATE_BASE64: ${{ inputs.build-certificate-base64 }}
P12_PASSWORD: ${{ inputs.p12-password }}
KEYCHAIN_PASSWORD: ${{ inputs.keychain-password }}
shell: bash
# Source:
# https://docs.github.com/en/actions/deployment/deploying-xcode-applications/installing-an-apple-certificate-on-macos-runners-for-xcode-development
run: |
# Create variables
CERTIFICATE_PATH=$RUNNER_TEMP/build_certificate.p12
KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db
# Import certificate from secrets
echo -n "$BUILD_CERTIFICATE_BASE64" | base64 --decode -o $CERTIFICATE_PATH
# Create temporary keychain
security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
security set-keychain-settings -lut 21600 $KEYCHAIN_PATH
security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
# Import certificate to keychain
security import $CERTIFICATE_PATH -P "$P12_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH
security list-keychain -d user -s $KEYCHAIN_PATH
116 changes: 116 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
name: Run tests and deploy to TestFlight

on:
workflow_dispatch:
# Uncomment this to run the workflow on every push to the main branch:
# push:
# branches: ["main"]

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

env:
XCODE_PROJECT: TemplateApp.xcodeproj
SCHEME: TemplateApp
TEST_PLAN: AllTests
TEST_RESULT_BUNDLE: TestResults.xcresult
ARCHIVE_PATH: TemplateApp.xcarchive
EXPORT_OPTIONS_PLIST: ExportOptions.plist

jobs:
build:
name: Run tests and deploy to TestFlight
runs-on: [self-hosted, macOS]

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Cache Swift Package Manager dependencies
uses: actions/cache@v4
with:
path: ${{ runner.temp }}/SourcePackages
key: ${{ runner.os }}-spm-${{ hashFiles('**/Package.resolved') }}
restore-keys: |
${{ runner.os }}-spm-
- name: Install Swift packages
run: |
xcodebuild -project "${{ env.XCODE_PROJECT }}" \
-scheme "${{ env.SCHEME }}" \
-onlyUsePackageVersionsFromResolvedFile \
-resolvePackageDependencies \
-clonedSourcePackagesDirPath "${{ runner.temp }}/SourcePackages"
- name: Install code signing certificate
uses: "./.github/actions/code-signing-setup"
with:
build-certificate-base64: ${{ secrets.BUILD_CERTIFICATE_BASE64 }}
p12-password: ${{ secrets.P12_PASSWORD }}
keychain-password: ${{ secrets.KEYCHAIN_PASSWORD }}

- name: Install App Store Connect API key
run: |
echo -n "${{ secrets.APP_STORE_CONNECT_API_KEY_BASE64 }}" | base64 --decode -o "${{ runner.temp }}/AuthKey.p8"
- name: Run tests
env:
PLATFORM: ${{ 'iOS Simulator' }}
run: |
# xcrun xctrace returns via stderr, not the expected stdout (see https://developer.apple.com/forums/thread/663959)
DEVICE=`xcrun xctrace list devices 2>&1 | grep -oE 'iPhone.*?[^\(]+' | head -1 | awk '{$1=$1;print}' | sed -e "s/ Simulator$//"`
xcodebuild test -project "${{ env.XCODE_PROJECT }}" \
-scheme "${{ env.SCHEME }}" \
-testPlan "${{ env.TEST_PLAN }}" \
-destination "platform=$PLATFORM,name=$DEVICE" \
-resultBundlePath "${{ env.TEST_RESULT_BUNDLE }}" \
-clonedSourcePackagesDirPath "${{ runner.temp }}/SourcePackages" \
-disableAutomaticPackageResolution
- name: Upload test result bundle
uses: actions/upload-artifact@v4
if: ${{ failure() }}
with:
name: ${{ env.SCHEME }}-${{ github.ref_name }}-${{ github.sha }}.xcresult
path: ${{ env.TEST_RESULT_BUNDLE }}

- name: Archive build
run: |
xcodebuild clean archive -project "${{ env.XCODE_PROJECT }}" \
-scheme "${{ env.SCHEME }}" \
-destination generic/platform=iOS \
-archivePath "${{ runner.temp }}/${{ env.ARCHIVE_PATH }}" \
-allowProvisioningUpdates \
-authenticationKeyPath "${{ runner.temp }}/AuthKey.p8" \
-authenticationKeyID ${{ secrets.APP_STORE_CONNECT_API_KEY_ID }} \
-authenticationKeyIssuerID ${{ secrets.APP_STORE_CONNECT_API_KEY_ISSUER_ID }} \
-clonedSourcePackagesDirPath "${{ runner.temp }}/SourcePackages" \
-disableAutomaticPackageResolution
- name: Upload to TestFlight
run: |
xcodebuild -exportArchive \
-allowProvisioningUpdates \
-authenticationKeyPath "${{ runner.temp }}/AuthKey.p8" \
-authenticationKeyID ${{ secrets.APP_STORE_CONNECT_API_KEY_ID }} \
-authenticationKeyIssuerID ${{ secrets.APP_STORE_CONNECT_API_KEY_ISSUER_ID }} \
-archivePath "${{ runner.temp }}/${{ env.ARCHIVE_PATH }}" \
-exportPath ${{ env.ARCHIVE_PATH }} \
-exportOptionsPlist ${{ env.EXPORT_OPTIONS_PLIST }}
# To add Crashlytics dSYM upload, grap the `upload-symbols` script from the Firebase SDK from here:
# https://github.com/firebase/firebase-ios-sdk/blob/main/Crashlytics/upload-symbols
# and add it to the repository in a folder called `scripts`. Then uncomment the following lines:

# - name: Upload dSYM files to Firebase Crashlytics
# run: |
# ./scripts/upload-symbols -p ios \
# -gsp "TemplateApp/GoogleService-Info.plist" \
# "${{ runner.temp }}/${{ env.ARCHIVE_PATH }}/dSYMs"

- name: Upload build artifact
uses: actions/upload-artifact@v4
with:
name: ${{ env.SCHEME }}-${{ github.ref_name }}-${{ github.sha }}.xcarchive
path: ${{ runner.temp }}/${{ env.ARCHIVE_PATH }}
68 changes: 68 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
name: Run tests

on:
pull_request:
branches: ["main"]

env:
XCODE_PROJECT: TemplateApp.xcodeproj
SCHEME: TemplateApp
TEST_PLAN: AllTests
TEST_RESULT_BUNDLE: TestResults.xcresult

jobs:
build:
name: Run tests
runs-on: [self-hosted, macOS]

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Cache Swift Package Manager dependencies
uses: actions/cache@v4
with:
path: ${{ runner.temp }}/SourcePackages
key: ${{ runner.os }}-spm-${{ hashFiles('**/Package.resolved') }}
restore-keys: |
${{ runner.os }}-spm-
- name: Install Swift packages
run: |
xcodebuild -project "${{ env.XCODE_PROJECT }}" \
-scheme "${{ env.SCHEME }}" \
-onlyUsePackageVersionsFromResolvedFile \
-resolvePackageDependencies \
-clonedSourcePackagesDirPath "${{ runner.temp }}/SourcePackages"
- name: Install code signing certificate
uses: "./.github/actions/code-signing-setup"
with:
build-certificate-base64: ${{ secrets.BUILD_CERTIFICATE_BASE64 }}
p12-password: ${{ secrets.P12_PASSWORD }}
keychain-password: ${{ secrets.KEYCHAIN_PASSWORD }}

- name: Install App Store Connect API key
run: |
echo -n "${{ secrets.APP_STORE_CONNECT_API_KEY_BASE64 }}" | base64 --decode -o "${{ runner.temp }}/AuthKey.p8"
- name: Run tests
env:
PLATFORM: ${{ 'iOS Simulator' }}
run: |
# xcrun xctrace returns via stderr, not the expected stdout (see https://developer.apple.com/forums/thread/663959)
DEVICE=`xcrun xctrace list devices 2>&1 | grep -oE 'iPhone.*?[^\(]+' | head -1 | awk '{$1=$1;print}' | sed -e "s/ Simulator$//"`
xcodebuild test -project "${{ env.XCODE_PROJECT }}" \
-scheme "${{ env.SCHEME }}" \
-testPlan "${{ env.TEST_PLAN }}" \
-destination "platform=$PLATFORM,name=$DEVICE" \
-resultBundlePath "${{ env.TEST_RESULT_BUNDLE }}" \
-clonedSourcePackagesDirPath "${{ runner.temp }}/SourcePackages" \
-disableAutomaticPackageResolution
- name: Upload test result bundle
uses: actions/upload-artifact@v4
if: ${{ failure() }}
with:
name: Xcode test results
path: ${{ env.TEST_RESULT_BUNDLE }}
3 changes: 3 additions & 0 deletions .swiftformat
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
--disable unusedArguments
--disable redundantRawValues
--indent 4
4 changes: 4 additions & 0 deletions .swiftlint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
disabled_rules:
- trailing_comma
- line_length
- unused_closure_parameter
22 changes: 22 additions & 0 deletions ExportOptions.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>destination</key>
<string>upload</string>
<key>manageAppVersionAndBuildNumber</key>
<true/>
<key>method</key>
<string>app-store</string>
<key>signingStyle</key>
<string>automatic</string>
<key>stripSwiftSymbols</key>
<true/>
<key>teamID</key>
<string>XXXXXXXXXX</string>
<key>testFlightInternalTestingOnly</key>
<false/>
<key>uploadSymbols</key>
<true/>
</dict>
</plist>
45 changes: 45 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Q42 iOS template

This is a template for creating iOS projects at Q42. It has opinionated defaults and boilerplate, based on how we do iOS projects at Q42.

## How to use it

1. In GitHub, press "use this template" to create a new repository.
2. Rename your project using the included Python script.

## Features

Only basic features that almost all projects use, were added in this template:

* SwiftUI using the SwiftUI lifecycle with an AppDelegate
* Dependency injection using Factory
* Unit tests and UI tests using Salad
* GitHub Actions CI configuration that runs the tests and submits the app to TestFlight

Xcode 15.3 or higher is required.

## Code style

The Xcode project is configured to use 4 spaces for indentation.
For linting Swift source code, we use [SwiftLint](https://github.com/realm/SwiftLint).
A configuration for [SwiftFormat](http://github.com/nicklockwood/SwiftFormat) is also included.

## Continuous integration

GitHub Actions is used for continuous integration (CI). The CI runs the automated tests when you make a pull request.
On a push to the `main` branch, it will also run the tests, and if they pass, a build of the app is made and uploaded to TestFlight.

### CI configuration

Five environment secrets are needed for the workflow to run on GitHub Actions.
You may configure these in the repository secret settings on GitHub.

* `BUILD_CERTIFICATE_BASE64` contains a base64-encoded string of the .p12 certificate bundle, used to code sign the app. This bundle needs to contain two certificates: **development** and **distribution**.
* `P12_PASSWORD` contains the password of the certificate bundle.
* `APP_STORE_CONNECT_API_KEY_BASE64` contains a base64-encoded string of the .p8 App Store Connect API key.
* `APP_STORE_CONNECT_API_KEY_ID` contains the key ID of the App Store Connect API key.
* `APP_STORE_CONNECT_API_KEY_ISSUER_ID` contains the issuer ID of the App Store Connect API key.

To create such a certificate bundle, open Keychain Access. Unfold the entries for the development and distribution certificate. Select the certificates and their private keys using shift, then right-click and select "Export 4 items...".

You can encode a file to base64 on the command line like this: `base64 -i ~/Desktop/Certificates.p12 | pbcopy`. This automatically puts the result on your clipboard.
Loading

0 comments on commit e708234

Please sign in to comment.