Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

May 2024 template project update #1

Merged
merged 4 commits into from
May 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
119 changes: 119 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
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: [macos-latest]

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

- name: Select Xcode version
run: sudo xcode-select --switch /Applications/Xcode_15.3.app

- 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 }}
60 changes: 60 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
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: [macos-latest]

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

- name: Select Xcode version
run: sudo xcode-select --switch /Applications/Xcode_15.3.app

- 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: 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 }}
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