Skip to content

Build Loop (main)

Build Loop (main) #58

Workflow file for this run

name: 4. Build Loop
run-name: Build Loop (${{ github.ref_name }})
on:
workflow_dispatch:
schedule:
# Check for updates every Sunday
# Later logic builds if there are updates or if it is the 2nd Sunday of the month
- cron: "33 7 * * 0" # Sunday at UTC 7:33
env:
GH_PAT: ${{ secrets.GH_PAT }}
UPSTREAM_REPO: LoopKit/LoopWorkspace
UPSTREAM_BRANCH: ${{ github.ref_name }} # branch on upstream repository to sync from (replace with specific branch name if needed)
TARGET_BRANCH: ${{ github.ref_name }} # target branch on fork to be kept in sync
jobs:
# use a single runner for these sequential steps
check_status:
runs-on: ubuntu-latest
name: Check status to decide whether to build
permissions:
contents: write
outputs:
NEW_COMMITS: ${{ steps.sync.outputs.has_new_commits }}
IS_SECOND_IN_MONTH: ${{ steps.date-check.outputs.is_second_instance }}
# Check GH_PAT, sync repository, check day in month
steps:
- name: Access
id: workflow-permission
run: |
# Validate Access Token
# Ensure that gh exit codes are handled when output is piped.
set -o pipefail
# Define patterns to validate the access token (GH_PAT) and distinguish between classic and fine-grained tokens.
GH_PAT_CLASSIC_PATTERN='^ghp_[a-zA-Z0-9]{36}$'
GH_PAT_FINE_GRAINED_PATTERN='^github_pat_[a-zA-Z0-9]{22}_[a-zA-Z0-9]{59}$'
# Validate Access Token (GH_PAT)
if [ -z "$GH_PAT" ]; then
failed=true
echo "::error::The GH_PAT secret is unset or empty. Set it and try again."
else
if [[ $GH_PAT =~ $GH_PAT_CLASSIC_PATTERN ]]; then
provides_scopes=true
echo "The GH_PAT secret is a structurally valid classic token."
elif [[ $GH_PAT =~ $GH_PAT_FINE_GRAINED_PATTERN ]]; then
echo "The GH_PAT secret is a structurally valid fine-grained token."
else
unknown_format=true
echo "The GH_PAT secret does not have a known token format."
fi
# Attempt to capture the x-oauth-scopes scopes of the token.
if ! scopes=$(curl -sS -f -I -H "Authorization: token $GH_PAT" https://api.github.com | { grep -i '^x-oauth-scopes:' || true; } | cut -d ' ' -f2- | tr -d '\r'); then
failed=true
if [ $unknown_format ]; then
echo "::error::Unable to connect to GitHub using the GH_PAT secret. Verify that it is set correctly (including the 'ghp_' or 'github_pat_' prefix) and try again."
else
echo "::error::Unable to connect to GitHub using the GH_PAT secret. Verify that the token exists and has not expired at https://github.com/settings/tokens. If necessary, regenerate or create a new token (and update the secret), then try again."
fi
elif [[ $scopes =~ workflow ]]; then
echo "The GH_PAT secret has repo and workflow permissions."
echo "has_permission=true" >> $GITHUB_OUTPUT
elif [[ $scopes =~ repo ]]; then
echo "The GH_PAT secret has repo (but not workflow) permissions."
elif [ $provides_scopes ]; then
failed=true
if [ -z "$scopes" ]; then
echo "The GH_PAT secret is valid and can be used to connect to GitHub, but it does not provide any permission scopes."
else
echo "The GH_PAT secret is valid and can be used to connect to GitHub, but it only provides the following permission scopes: $scopes"
fi
echo "::error::The GH_PAT secret is lacking at least the 'repo' permission scope required to access the Match-Secrets repository. Update the token permissions at https://github.com/settings/tokens (to include the 'repo' and 'workflow' scopes) and try again."
else
echo "The GH_PAT secret is valid and can be used to connect to GitHub, but it does not provide inspectable scopes. Assuming that the 'repo' and 'workflow' permission scopes required to access the Match-Secrets repository and perform automations are present."
echo "has_permission=true" >> $GITHUB_OUTPUT
fi
fi
# Exit unsuccessfully if secret validation failed.
if [ $failed ]; then
exit 2
fi
- name: Checkout target repo
if: |
steps.workflow-permission.outputs.has_permission == 'true' &&
(vars.SCHEDULED_BUILD != 'false' || vars.SCHEDULED_SYNC != 'false')
uses: actions/checkout@v4
with:
token: ${{ secrets.GH_PAT }}
# This syncs any target branch to upstream branch of the same name
- name: Sync upstream changes
if: | # do not run the upstream sync action on the upstream repository
steps.workflow-permission.outputs.has_permission == 'true' &&
vars.SCHEDULED_SYNC != 'false' && github.repository_owner != 'loopandlearn'
id: sync
uses: aormsby/Fork-Sync-With-Upstream-action@v3.4.1
with:
target_sync_branch: ${{ env.TARGET_BRANCH }}
shallow_since: 6 months ago
target_repo_token: ${{ secrets.GH_PAT }}
upstream_sync_branch: ${{ env.UPSTREAM_BRANCH }}
upstream_sync_repo: ${{ env.UPSTREAM_REPO }}
# Display a sample message based on the sync output var 'has_new_commits'
- name: New commits found
if: |
steps.workflow-permission.outputs.has_permission == 'true' &&
vars.SCHEDULED_SYNC != 'false' && steps.sync.outputs.has_new_commits == 'true'
run: echo "New commits were found to sync."
- name: No new commits
if: |
steps.workflow-permission.outputs.has_permission == 'true' &&
vars.SCHEDULED_SYNC != 'false' && steps.sync.outputs.has_new_commits == 'false'
run: echo "There were no new commits."
- name: Show value of 'has_new_commits'
if: steps.workflow-permission.outputs.has_permission == 'true' && vars.SCHEDULED_SYNC != 'false'
run: |
echo ${{ steps.sync.outputs.has_new_commits }}
echo "NEW_COMMITS=${{ steps.sync.outputs.has_new_commits }}" >> $GITHUB_OUTPUT
- name: Show scheduled build configuration message
if: steps.workflow-permission.outputs.has_permission != 'true'
run: |
echo "### :calendar: Scheduled Sync and Build Disabled :mobile_phone_off:" >> $GITHUB_STEP_SUMMARY
echo "You have not yet configured the scheduled sync and build for Loop's browser build." >> $GITHUB_STEP_SUMMARY
echo "Synchronizing your fork of <code>LoopWorkspace</code> with the upstream repository <code>LoopKit/LoopWorkspace</code> will be skipped." >> $GITHUB_STEP_SUMMARY
echo "If you want to enable automatic builds and updates for your Loop, please follow the instructions \
under the following path <code>LoopWorkspace/fastlane/testflight.md</code>." >> $GITHUB_STEP_SUMMARY
# Set a logic flag if this is the second instance of this day-of-week in this month
- name: Check if this is the second time this day-of-week happens this month
id: date-check
run: |
DAY_OF_MONTH=$(date +%-d)
WEEK_OF_MONTH=$(( ($(date +%-d) - 1) / 7 + 1 ))
if [[ $WEEK_OF_MONTH -eq 2 ]]; then
echo "is_second_instance=true" >> "$GITHUB_OUTPUT"
else
echo "is_second_instance=false" >> "$GITHUB_OUTPUT"
fi
# Checks if Distribution certificate is present and valid, optionally nukes and
# creates new certs if the repository variable ENABLE_NUKE_CERTS == 'true'
# only run if a build is planned
check_certs:
needs: [check_status]
name: Check certificates
uses: ./.github/workflows/create_certs.yml
secrets: inherit
if: |
github.event_name == 'workflow_dispatch' ||
(vars.SCHEDULED_BUILD != 'false' && needs.check_status.outputs.IS_SECOND_IN_MONTH == 'true') ||
(vars.SCHEDULED_SYNC != 'false' && needs.check_status.outputs.NEW_COMMITS == 'true' )
# Builds Loop
build:
name: Build
needs: [check_certs, check_status]
runs-on: macos-15
permissions:
contents: write
if:
| # builds with manual start; if scheduled: once a month or when new commits are found
github.event_name == 'workflow_dispatch' ||
(vars.SCHEDULED_BUILD != 'false' && needs.check_status.outputs.IS_SECOND_IN_MONTH == 'true') ||
(vars.SCHEDULED_SYNC != 'false' && needs.check_status.outputs.NEW_COMMITS == 'true' )
steps:
- name: Select Xcode version
run: "sudo xcode-select --switch /Applications/Xcode_16.4.app/Contents/Developer"
- name: Checkout Repo for building
uses: actions/checkout@v4
with:
token: ${{ secrets.GH_PAT }}
submodules: recursive
ref: ${{ env.TARGET_BRANCH }}
# Customize Loop: Download and apply patches
- name: Customize Loop
run: |
# LoopWorkspace patches
# -applies any patches located in the LoopWorkspace/patches/ directory
if $(ls ./patches/* &> /dev/null); then
git apply ./patches/* --allow-empty -v --whitespace=fix
fi
# Submodule Loop patches:
# Template for customizing submodule Loop (changes Loop app name to "CustomLoop")
# Remove the "#" sign from the beginning of the line below to activate:
#curl https://github.com/loopnlearn/Loop/commit/d206432b024279ef710df462b20bd464cd9682d4.patch | git apply --directory=Loop -v --whitespace=fix
# Submodule LoopKit patches:
# General template for customizing submodule LoopKit
# Copy url from a GitHub commit or pull request and insert below, and remove the "#" sign from the beginning of the line to activate:
#curl url_to_github_commit.patch | git apply --directory=LoopKit -v --whitespace=fix
# Submodule xxxxx patches:
# Add patches for customization of additional submodules by following the templates above,
# and make sure to specify the submodule by setting "--directory=(submodule_name)".
# Several patches may be added per submodule.
# Adding comments (#) may be useful to easily tell the individual patches apart.
# Patch Fastlane Match to not print tables
- name: Patch Match Tables
run: |
TABLE_PRINTER_PATH=$(ruby -e 'puts Gem::Specification.find_by_name("fastlane").gem_dir')/match/lib/match/table_printer.rb
if [ -f "$TABLE_PRINTER_PATH" ]; then
sed -i "" "/puts(Terminal::Table.new(params))/d" "$TABLE_PRINTER_PATH"
else
echo "table_printer.rb not found"
exit 1
fi
# Install project dependencies
- name: Install Project Dependencies
run: bundle install
# Sync the GitHub runner clock with the Windows time server (workaround as suggested in https://github.com/actions/runner/issues/2996)
- name: Sync clock
run: sudo sntp -sS time.windows.com
# Build signed Loop IPA file
- name: Fastlane Build & Archive
run: bundle exec fastlane build_loop
env:
TEAMID: ${{ secrets.TEAMID }}
GH_PAT: ${{ secrets.GH_PAT }}
FASTLANE_KEY_ID: ${{ secrets.FASTLANE_KEY_ID }}
FASTLANE_ISSUER_ID: ${{ secrets.FASTLANE_ISSUER_ID }}
FASTLANE_KEY: ${{ secrets.FASTLANE_KEY }}
MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
# Upload to TestFlight
- name: Fastlane upload to TestFlight
run: bundle exec fastlane release
env:
TEAMID: ${{ secrets.TEAMID }}
GH_PAT: ${{ secrets.GH_PAT }}
FASTLANE_KEY_ID: ${{ secrets.FASTLANE_KEY_ID }}
FASTLANE_ISSUER_ID: ${{ secrets.FASTLANE_ISSUER_ID }}
FASTLANE_KEY: ${{ secrets.FASTLANE_KEY }}
MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
# Upload Build artifacts
- name: Upload build log, IPA and Symbol artifacts
if: always()
uses: actions/upload-artifact@v4
with:
name: build-artifacts
path: |
artifacts
buildlog