Skip to content

Commit 9161e43

Browse files
committed
streamline build actions
1 parent 35d04cc commit 9161e43

File tree

2 files changed

+118
-197
lines changed

2 files changed

+118
-197
lines changed

.github/workflows/build_loopcaregiver.yml

Lines changed: 104 additions & 178 deletions
Original file line numberDiff line numberDiff line change
@@ -2,150 +2,107 @@ name: 4. Build LoopCaregiver
22
run-name: Build LoopCaregiver (${{ github.ref_name }})
33
on:
44
workflow_dispatch:
5-
6-
## Remove the "#" sign from the beginning of the line below to get automated builds on push (code changes in your repository)
7-
#push:
8-
9-
# Automated builds now include automatic certificate update - the nuke certs part of that process could
10-
# affect other OS apps if run simultaneously.
11-
# Each OS needs a time of day distinct from other apps, LoopCaregiver uses 13 every Wed and 11 every 1st of month
125
schedule:
13-
# avoid starting an action at xx:00 when GitHub resources are more likely to be impacted
14-
- cron: "33 13 * * 3" # Checks for updates at 09:33 UTC every Wednesday
15-
- cron: "33 11 1 * *" # Builds the app on the 1st of every month at 07:33 UTC
6+
# Check for updates every Sunday
7+
# Later logic builds if there are updates or if it is the 2nd Sunday of the month
8+
- cron: "33 13 * * 0" # Sunday at UTC 13:33
169

1710
env:
11+
GH_PAT: ${{ secrets.GH_PAT }}
1812
UPSTREAM_REPO: LoopKit/LoopCaregiver
1913
UPSTREAM_BRANCH: ${{ github.ref_name }} # branch on upstream repository to sync from (replace with specific branch name if needed)
20-
TARGET_BRANCH: ${{ github.ref_name }} # target branch on fork to be kept in sync, and target branch on upstream to be kept alive (replace with specific branch name if needed)
21-
ALIVE_BRANCH_MAIN: alive-main # Will not be created: LoopCaregiver has no main branch
22-
ALIVE_BRANCH_DEV: alive-dev
14+
TARGET_BRANCH: ${{ github.ref_name }} # target branch on fork to be kept in sync
2315

2416
jobs:
25-
# Checks if Distribution certificate is present and valid, optionally nukes and
26-
# creates new certs if the repository variable ENABLE_NUKE_CERTS == 'true'
27-
check_certs:
28-
name: Check certificates
29-
uses: ./.github/workflows/create_certs.yml
30-
secrets: inherit
31-
32-
# Checks if GH_PAT holds workflow permissions
33-
# Checks for existence of alive branch; if non-existent creates it
34-
check_alive_and_permissions:
35-
needs: check_certs
17+
# use a single runner for these sequential steps
18+
check_status:
3619
runs-on: ubuntu-latest
37-
name: Check alive branch and permissions
20+
name: Check status to decide whether to build
3821
permissions:
3922
contents: write
4023
outputs:
41-
WORKFLOW_PERMISSION: ${{ steps.workflow-permission.outputs.has_permission }}
24+
NEW_COMMITS: ${{ steps.sync.outputs.has_new_commits }}
25+
IS_SECOND_IN_MONTH: ${{ steps.date-check.outputs.is_second_instance }}
4226

27+
# Check GH_PAT, sync repository, check day in month
4328
steps:
44-
- name: Check for workflow permissions
45-
id: workflow-permission
46-
env:
47-
TOKEN_TO_CHECK: ${{ secrets.GH_PAT }}
48-
run: |
49-
PERMISSIONS=$(curl -sS -f -I -H "Authorization: token ${{ env.TOKEN_TO_CHECK }}" https://api.github.com | grep ^x-oauth-scopes: | cut -d' ' -f2-);
5029

51-
if [[ $PERMISSIONS =~ "workflow" || $PERMISSIONS == "" ]]; then
52-
echo "GH_PAT holds workflow permissions or is fine-grained PAT."
53-
echo "has_permission=true" >> $GITHUB_OUTPUT # Set WORKFLOW_PERMISSION to false.
54-
else
55-
echo "GH_PAT lacks workflow permissions."
56-
echo "Automated build features will be skipped!"
57-
echo "has_permission=false" >> $GITHUB_OUTPUT # Set WORKFLOW_PERMISSION to false.
58-
fi
59-
60-
- name: Check for alive branches
61-
if: steps.workflow-permission.outputs.has_permission == 'true'
62-
env:
63-
GITHUB_TOKEN: ${{ secrets.GH_PAT }}
30+
- name: Access
31+
id: workflow-permission
6432
run: |
65-
if [[ $(gh api -H "Accept: application/vnd.github+json" /repos/${{ github.repository_owner }}/LoopCaregiver/branches | jq --raw-output '[.[] | select(.name == "alive-main" or .name == "alive-dev")] | length > 0') == "true" ]]; then
66-
echo "Branches 'alive-main' or 'alive-dev' exist."
67-
echo "ALIVE_BRANCH_EXISTS=true" >> $GITHUB_ENV
33+
# Validate Access Token
34+
35+
# Ensure that gh exit codes are handled when output is piped.
36+
set -o pipefail
37+
38+
# Define patterns to validate the access token (GH_PAT) and distinguish between classic and fine-grained tokens.
39+
GH_PAT_CLASSIC_PATTERN='^ghp_[a-zA-Z0-9]{36}$'
40+
GH_PAT_FINE_GRAINED_PATTERN='^github_pat_[a-zA-Z0-9]{22}_[a-zA-Z0-9]{59}$'
41+
42+
# Validate Access Token (GH_PAT)
43+
if [ -z "$GH_PAT" ]; then
44+
failed=true
45+
echo "::error::The GH_PAT secret is unset or empty. Set it and try again."
6846
else
69-
echo "Branches 'alive-main' and 'alive-dev' do not exist."
70-
echo "ALIVE_BRANCH_EXISTS=false" >> $GITHUB_ENV
47+
if [[ $GH_PAT =~ $GH_PAT_CLASSIC_PATTERN ]]; then
48+
provides_scopes=true
49+
echo "The GH_PAT secret is a structurally valid classic token."
50+
elif [[ $GH_PAT =~ $GH_PAT_FINE_GRAINED_PATTERN ]]; then
51+
echo "The GH_PAT secret is a structurally valid fine-grained token."
52+
else
53+
unknown_format=true
54+
echo "The GH_PAT secret does not have a known token format."
55+
fi
56+
57+
# Attempt to capture the x-oauth-scopes scopes of the token.
58+
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
59+
failed=true
60+
if [ $unknown_format ]; then
61+
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."
62+
else
63+
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."
64+
fi
65+
elif [[ $scopes =~ workflow ]]; then
66+
echo "The GH_PAT secret has repo and workflow permissions."
67+
echo "has_permission=true" >> $GITHUB_OUTPUT
68+
elif [[ $scopes =~ repo ]]; then
69+
echo "The GH_PAT secret has repo (but not workflow) permissions."
70+
elif [ $provides_scopes ]; then
71+
failed=true
72+
if [ -z "$scopes" ]; then
73+
echo "The GH_PAT secret is valid and can be used to connect to GitHub, but it does not provide any permission scopes."
74+
else
75+
echo "The GH_PAT secret is valid and can be used to connect to GitHub, but it only provides the following permission scopes: $scopes"
76+
fi
77+
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."
78+
else
79+
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."
80+
echo "has_permission=true" >> $GITHUB_OUTPUT
81+
fi
7182
fi
72-
73-
- name: Create alive branches
74-
if: env.ALIVE_BRANCH_EXISTS == 'false'
75-
env:
76-
GITHUB_TOKEN: ${{ secrets.GH_PAT }}
77-
run: |
78-
# Get ref for LoopKit/LoopCaregiver:main
79-
SHA_MAIN=$(curl -sS -H "Authorization: token $GITHUB_TOKEN" https://api.github.com/repos/${{ env.UPSTREAM_REPO }}/git/refs/heads/main | jq -r '.object.sha')
80-
81-
# Get ref for LoopKit/LoopCaregiver:dev
82-
SHA_DEV=$(curl -sS -H "Authorization: token $GITHUB_TOKEN" https://api.github.com/repos/${{ env.UPSTREAM_REPO }}/git/refs/heads/dev | jq -r '.object.sha')
83-
84-
# N/A for LoopCaregiver: Create alive-main branch based on LoopKit/LoopCaregiver:main
85-
#gh api \
86-
# --method POST \
87-
# -H "Authorization: token $GITHUB_TOKEN" \
88-
# -H "Accept: application/vnd.github.v3+json" \
89-
# /repos/${{ github.repository_owner }}/LoopCaregiver/git/refs \
90-
# -f ref='refs/heads/alive-main' \
91-
# -f sha=$SHA_MAIN
92-
93-
# Create alive-dev branch based on LoopKit/LoopCaregiver:dev
94-
gh api \
95-
--method POST \
96-
-H "Authorization: token $GITHUB_TOKEN" \
97-
-H "Accept: application/vnd.github.v3+json" \
98-
/repos/${{ github.repository_owner }}/LoopCaregiver/git/refs \
99-
-f ref='refs/heads/alive-dev' \
100-
-f sha=$SHA_DEV
101-
102-
# Checks for changes in upstream repository; if changes exist prompts sync for build
103-
# Performs keepalive to avoid stale fork
104-
check_latest_from_upstream:
105-
needs: [check_certs, check_alive_and_permissions]
106-
runs-on: ubuntu-latest
107-
name: Check upstream and keep alive
108-
outputs:
109-
NEW_COMMITS: ${{ steps.sync.outputs.has_new_commits }}
110-
ABORT_SYNC: ${{ steps.check_branch.outputs.ABORT_SYNC }}
111-
112-
steps:
113-
- name: Check if running on main or dev branch
114-
if: |
115-
needs.check_alive_and_permissions.outputs.WORKFLOW_PERMISSION == 'true' &&
116-
(vars.SCHEDULED_BUILD != 'false' || vars.SCHEDULED_SYNC != 'false')
117-
id: check_branch
118-
run: |
119-
if [ "${GITHUB_REF##*/}" = "main" ]; then
120-
echo "Running on main branch"
121-
echo "ALIVE_BRANCH=${ALIVE_BRANCH_MAIN}" >> $GITHUB_OUTPUT
122-
echo "ABORT_SYNC=false" >> $GITHUB_OUTPUT
123-
elif [ "${GITHUB_REF##*/}" = "dev" ]; then
124-
echo "Running on dev branch"
125-
echo "ALIVE_BRANCH=${ALIVE_BRANCH_DEV}" >> $GITHUB_OUTPUT
126-
echo "ABORT_SYNC=false" >> $GITHUB_OUTPUT
127-
else
128-
echo "Not running on main or dev branch"
129-
echo "ABORT_SYNC=true" >> $GITHUB_OUTPUT
83+
84+
# Exit unsuccessfully if secret validation failed.
85+
if [ $failed ]; then
86+
exit 2
13087
fi
13188
13289
- name: Checkout target repo
13390
if: |
134-
needs.check_alive_and_permissions.outputs.WORKFLOW_PERMISSION == 'true' &&
91+
steps.workflow-permission.outputs.has_permission == 'true' &&
13592
(vars.SCHEDULED_BUILD != 'false' || vars.SCHEDULED_SYNC != 'false')
13693
uses: actions/checkout@v4
13794
with:
13895
token: ${{ secrets.GH_PAT }}
139-
ref: ${{ steps.check_branch.outputs.ALIVE_BRANCH }}
14096

97+
# This syncs any target branch to upstream branch of the same name
14198
- name: Sync upstream changes
14299
if: | # do not run the upstream sync action on the upstream repository
143-
needs.check_alive_and_permissions.outputs.WORKFLOW_PERMISSION == 'true' &&
144-
vars.SCHEDULED_SYNC != 'false' && github.repository_owner != 'LoopKit' && steps.check_branch.outputs.ABORT_SYNC == 'false'
100+
steps.workflow-permission.outputs.has_permission == 'true' &&
101+
vars.SCHEDULED_SYNC != 'false' && github.repository_owner != 'loopandlearn'
145102
id: sync
146103
uses: aormsby/Fork-Sync-With-Upstream-action@v3.4.1
147104
with:
148-
target_sync_branch: ${{ steps.check_branch.outputs.ALIVE_BRANCH }}
105+
target_sync_branch: ${{ env.TARGET_BRANCH }}
149106
shallow_since: 6 months ago
150107
target_repo_token: ${{ secrets.GH_PAT }}
151108
upstream_sync_branch: ${{ env.UPSTREAM_BRANCH }}
@@ -154,102 +111,71 @@ jobs:
154111
# Display a sample message based on the sync output var 'has_new_commits'
155112
- name: New commits found
156113
if: |
157-
needs.check_alive_and_permissions.outputs.WORKFLOW_PERMISSION == 'true' &&
114+
steps.workflow-permission.outputs.has_permission == 'true' &&
158115
vars.SCHEDULED_SYNC != 'false' && steps.sync.outputs.has_new_commits == 'true'
159116
run: echo "New commits were found to sync."
160117

161118
- name: No new commits
162119
if: |
163-
needs.check_alive_and_permissions.outputs.WORKFLOW_PERMISSION == 'true' &&
120+
steps.workflow-permission.outputs.has_permission == 'true' &&
164121
vars.SCHEDULED_SYNC != 'false' && steps.sync.outputs.has_new_commits == 'false'
165122
run: echo "There were no new commits."
166123

167124
- name: Show value of 'has_new_commits'
168-
if: needs.check_alive_and_permissions.outputs.WORKFLOW_PERMISSION == 'true' && vars.SCHEDULED_SYNC != 'false' && steps.check_branch.outputs.ABORT_SYNC == 'false'
125+
if: steps.workflow-permission.outputs.has_permission == 'true' && vars.SCHEDULED_SYNC != 'false'
169126
run: |
170127
echo ${{ steps.sync.outputs.has_new_commits }}
171128
echo "NEW_COMMITS=${{ steps.sync.outputs.has_new_commits }}" >> $GITHUB_OUTPUT
172129
173-
# Keep repository "alive": add empty commits to ALIVE_BRANCH after "time_elapsed" days of inactivity to avoid inactivation of scheduled workflows
174-
- name: Keep alive
175-
run: |
176-
echo "Keep Alive is no longer available"
177-
# if: |
178-
# needs.check_alive_and_permissions.outputs.WORKFLOW_PERMISSION == 'true' &&
179-
# (vars.SCHEDULED_BUILD != 'false' || vars.SCHEDULED_SYNC != 'false')
180-
# uses: gautamkrishnar/keepalive-workflow@v1 # using the workflow with default settings
181-
# with:
182-
# time_elapsed: 20 # Time elapsed from the previous commit to trigger a new automated commit (in days)
183-
184130
- name: Show scheduled build configuration message
185-
if: needs.check_alive_and_permissions.outputs.WORKFLOW_PERMISSION != 'true'
131+
if: steps.workflow-permission.outputs.has_permission != 'true'
186132
run: |
187133
echo "### :calendar: Scheduled Sync and Build Disabled :mobile_phone_off:" >> $GITHUB_STEP_SUMMARY
188134
echo "You have not yet configured the scheduled sync and build for LoopCaregiver's browser build." >> $GITHUB_STEP_SUMMARY
189135
echo "Synchronizing your fork of <code>LoopCaregiver</code> with the upstream repository <code>LoopKit/LoopCaregiver</code> will be skipped." >> $GITHUB_STEP_SUMMARY
190136
echo "If you want to enable automatic builds and updates for your LoopCaregiver, please follow the instructions \
191137
under the following path <code>LoopCaregiver/fastlane/testflight.md</code>." >> $GITHUB_STEP_SUMMARY
192138
139+
# Set a logic flag if this is the second instance of this day-of-week in this month
140+
- name: Check if this is the second time this day-of-week happens this month
141+
id: date-check
142+
run: |
143+
DAY_OF_MONTH=$(date +%-d)
144+
WEEK_OF_MONTH=$(( ($(date +%-d) - 1) / 7 + 1 ))
145+
if [[ $WEEK_OF_MONTH -eq 2 ]]; then
146+
echo "is_second_instance=true" >> "$GITHUB_OUTPUT"
147+
else
148+
echo "is_second_instance=false" >> "$GITHUB_OUTPUT"
149+
fi
150+
151+
# Checks if Distribution certificate is present and valid, optionally nukes and
152+
# creates new certs if the repository variable ENABLE_NUKE_CERTS == 'true'
153+
# only run if a build is planned
154+
check_certs:
155+
needs: [check_status]
156+
name: Check certificates
157+
uses: ./.github/workflows/create_certs.yml
158+
secrets: inherit
159+
if: |
160+
github.event_name == 'workflow_dispatch' ||
161+
(vars.SCHEDULED_BUILD != 'false' && needs.check_status.outputs.IS_SECOND_IN_MONTH == 'true') ||
162+
(vars.SCHEDULED_SYNC != 'false' && needs.check_status.outputs.NEW_COMMITS == 'true' )
163+
193164
# Builds LoopCaregiver
194165
build:
195166
name: Build
196-
needs: [check_certs, check_alive_and_permissions, check_latest_from_upstream]
167+
needs: [check_certs, check_status]
197168
runs-on: macos-15
198169
permissions:
199170
contents: write
200171
if:
201-
| # runs if started manually, or if sync schedule is set and enabled and scheduled on the first Saturday each month, or if sync schedule is set and enabled and new commits were found
172+
| # builds with manual start; if scheduled: once a month or when new commits are found
202173
github.event_name == 'workflow_dispatch' ||
203-
(needs.check_alive_and_permissions.outputs.WORKFLOW_PERMISSION == 'true' &&
204-
(vars.SCHEDULED_BUILD != 'false' && github.event.schedule == '33 11 1 * *') ||
205-
(vars.SCHEDULED_SYNC != 'false' && needs.check_latest_from_upstream.outputs.NEW_COMMITS == 'true' )
206-
)
174+
(vars.SCHEDULED_BUILD != 'false' && needs.check_status.outputs.IS_SECOND_IN_MONTH == 'true') ||
175+
(vars.SCHEDULED_SYNC != 'false' && needs.check_status.outputs.NEW_COMMITS == 'true' )
207176
steps:
208177
- name: Select Xcode version
209-
run: "sudo xcode-select --switch /Applications/Xcode_16.3.app/Contents/Developer"
210-
211-
- name: Checkout Repo for syncing
212-
if: |
213-
needs.check_alive_and_permissions.outputs.WORKFLOW_PERMISSION == 'true' &&
214-
vars.SCHEDULED_SYNC != 'false'
215-
uses: actions/checkout@v4
216-
with:
217-
token: ${{ secrets.GH_PAT }}
218-
ref: ${{ env.TARGET_BRANCH }}
219-
220-
- name: Sync upstream changes
221-
if: | # do not run the upstream sync action on the upstream repository
222-
needs.check_alive_and_permissions.outputs.WORKFLOW_PERMISSION == 'true' &&
223-
vars.SCHEDULED_SYNC != 'false' && github.repository_owner != 'LoopKit' && needs.check_latest_from_upstream.outputs.ABORT_SYNC == 'false'
224-
id: sync
225-
uses: aormsby/Fork-Sync-With-Upstream-action@v3.4.1
226-
with:
227-
target_sync_branch: ${{ env.TARGET_BRANCH }}
228-
shallow_since: 6 months ago
229-
target_repo_token: ${{ secrets.GH_PAT }}
230-
upstream_sync_branch: ${{ env.UPSTREAM_BRANCH }}
231-
upstream_sync_repo: ${{ env.UPSTREAM_REPO }}
232-
233-
# Display a sample message based on the sync output var 'has_new_commits'
234-
- name: New commits found
235-
if: |
236-
needs.check_alive_and_permissions.outputs.WORKFLOW_PERMISSION == 'true' &&
237-
vars.SCHEDULED_SYNC != 'false' && steps.sync.outputs.has_new_commits == 'true' && needs.check_latest_from_upstream.outputs.ABORT_SYNC == 'false'
238-
run: echo "New commits were found to sync."
239-
240-
- name: No new commits
241-
if: |
242-
needs.check_alive_and_permissions.outputs.WORKFLOW_PERMISSION == 'true' &&
243-
vars.SCHEDULED_SYNC != 'false' && steps.sync.outputs.has_new_commits == 'false' && needs.check_latest_from_upstream.outputs.ABORT_SYNC == 'false'
244-
run: echo "There were no new commits."
245-
246-
- name: Show value of 'has_new_commits'
247-
if: |
248-
needs.check_alive_and_permissions.outputs.WORKFLOW_PERMISSION == 'true'
249-
&& vars.SCHEDULED_SYNC != 'false' && needs.check_latest_from_upstream.outputs.ABORT_SYNC == 'false'
250-
run: |
251-
echo ${{ steps.sync.outputs.has_new_commits }}
252-
echo "NEW_COMMITS=${{ steps.sync.outputs.has_new_commits }}" >> $GITHUB_OUTPUT
178+
run: "sudo xcode-select --switch /Applications/Xcode_16.4.app/Contents/Developer"
253179

254180
- name: Checkout Repo for building
255181
uses: actions/checkout@v4

0 commit comments

Comments
 (0)