diff --git a/.github/workflows/build_loop.yml b/.github/workflows/build_loop.yml index 74cd09e56..bce7ef5d0 100644 --- a/.github/workflows/build_loop.yml +++ b/.github/workflows/build_loop.yml @@ -7,32 +7,105 @@ on: #push: schedule: - - cron: '0 04 * * *' # Checks for updates at 04:00 UTC every day - - cron: '0 04 1 * *' # Builds the app on the 1th every month + - cron: '0 8 * * 3' # Checks for updates at 08:00 am UTC every Wednesday + - cron: '0 8 1 * 6' # Builds the app on the 1st Saturday every month at 08:00 am UTC env: UPSTREAM_REPO: LoopKit/LoopWorkspace - UPSTREAM_BRANCH: ${{ github.ref_name }} # branch on upstream repository to sync from (relpace with specific branch name if needed) - TARGET_BRANCH: ${{ github.ref_name }} # target branch on fork to be kept in sync, and target branch on upstream to be kept alive (relpace with specific branch name if needed) + 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, and target branch on upstream to be kept alive (replace with specific branch name if needed) ALIVE_BRANCH: alive - SYNC_UPSTREAM: ${{ vars.SYNC_UPSTREAM }} # set an optional "SYNC_UPSTREAM" repository variable to 'false' to disable syncing of fork with the upstream repository + WORKFLOW_PERMISSIONS: false jobs: + secrets: + uses: ./.github/workflows/validate_secrets.yml + secrets: inherit + + # Checks if GH_PAT holds workflow permissions + # Checks for existence of alive branch; if non-existent creates it + check_alive_and_permissions: + needs: secrets + runs-on: ubuntu-latest + name: Check alive branch and permissions + permissions: + contents: write + outputs: + WORKFLOW_PERMISSION: ${{ steps.workflow-permission.outputs.has_permission }} + + steps: + - name: Check for workflow permissions + id: workflow-permission + env: + TOKEN_TO_CHECK: ${{ secrets.GH_PAT }} + run: | + PERMISSIONS=$(curl -sS -f -I -H "Authorization: token ${{ env.TOKEN_TO_CHECK }}" https://api.github.com | grep ^x-oauth-scopes: | cut -d' ' -f2-); + + if [[ $PERMISSIONS =~ "workflow" || $PERMISSIONS == "" ]]; then + echo "GH_PAT holds workflow permissions or is fine-grained PAT." + echo "has_permission=true" >> $GITHUB_OUTPUT # Set WORKFLOW_PERMISSION to false. + else + echo "GH_PAT lacks workflow permissions." + echo "Automated build features will be skipped!" + echo "has_permission=false" >> $GITHUB_OUTPUT # Set WORKFLOW_PERMISSION to false. + fi + + - name: Check for alive branch + if: steps.workflow-permission.outputs.has_permission == 'true' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + if [[ "$(gh api -H "Accept: application/vnd.github+json" /repos/${{ github.repository_owner }}/LoopWorkspace/branches | jq --raw-output 'any(.name=="alive")')" == "true" ]]; then + echo "Branch 'alive' exists." + echo "ALIVE_BRANCH_EXISTS=true" >> $GITHUB_ENV # Set ALIVE_BRANCH_EXISTS to true + else + echo "Branch 'alive' does not exist." + echo "ALIVE_BRANCH_EXISTS=false" >> $GITHUB_ENV # Set ALIVE_BRANCH_EXISTS to false + fi + + - name: Create alive branch + if: env.ALIVE_BRANCH_EXISTS != 'true' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + # Get ref for LoopKit/LoopWorkspace:dev + SHA=$(curl -sS https://api.github.com/repos/${{ env.UPSTREAM_REPO }}/git/refs \ + | jq '.[] | select(.ref == "refs/heads/dev" ) | .object.sha' \ + | tr -d '"' + ); + + # Create alive branch based on LoopKit/LoopWorkspace:dev + gh api \ + --method POST \ + -H "Authorization: token $GITHUB_TOKEN" \ + -H "Accept: application/vnd.github.v3+json" \ + /repos/${{ github.repository_owner }}/LoopWorkspace/git/refs \ + -f ref='refs/heads/alive' \ + -f sha=$SHA + + # Checks for changes in upstream repository; if changes exist prompts sync for build + # Performs keepalive to avoid stale fork check_latest_from_upstream: + needs: check_alive_and_permissions runs-on: ubuntu-latest name: Check upstream and keep alive outputs: NEW_COMMITS: ${{ steps.sync.outputs.has_new_commits }} - + steps: - name: Checkout target repo + if: | + needs.check_alive_and_permissions.outputs.WORKFLOW_PERMISSION == 'true' && + (vars.SCHEDULED_BUILD != 'false' || vars.SCHEDULED_SYNC != 'false') uses: actions/checkout@v3 with: token: ${{ secrets.GH_PAT }} ref: alive - name: Sync upstream changes - if: ${{ env.SYNC_UPSTREAM != 'false' && github.repository_owner != 'LoopKit' }} # do not run the upstream sync action on the upstream repository + if: | # do not run the upstream sync action on the upstream repository + needs.check_alive_and_permissions.outputs.WORKFLOW_PERMISSION == 'true' && + vars.SCHEDULED_SYNC != 'false' && github.repository_owner != 'LoopKit' id: sync uses: aormsby/Fork-Sync-With-Upstream-action@v3.4 with: @@ -44,41 +117,72 @@ jobs: # Display a sample message based on the sync output var 'has_new_commits' - name: New commits found - if: steps.sync.outputs.has_new_commits == 'true' + if: | + needs.check_alive_and_permissions.outputs.WORKFLOW_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.sync.outputs.has_new_commits == 'false' + if: | + needs.check_alive_and_permissions.outputs.WORKFLOW_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: needs.check_alive_and_permissions.outputs.WORKFLOW_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 - + # Keep repository "alive": add empty commits to ALIVE_BRANCH after "time_elapsed" days of inactivity to avoid inactivation of scheduled workflows - name: Keep alive + if: | + needs.check_alive_and_permissions.outputs.WORKFLOW_PERMISSION == 'true' && + (vars.SCHEDULED_BUILD != 'false' || vars.SCHEDULED_SYNC != 'false') uses: gautamkrishnar/keepalive-workflow@v1 # using the workflow with default settings with: time_elapsed: 20 # Time elapsed from the previous commit to trigger a new automated commit (in days) + + - name: Show scheduled build configuration message + if: needs.check_alive_and_permissions.outputs.WORKFLOW_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 LoopWorkspace with the upstream repository LoopKit/LoopWorkspace 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 LoopWorkspace/fastlane/testflight.md." >> $GITHUB_STEP_SUMMARY + + # Builds Loop build: name: Build needs: check_latest_from_upstream runs-on: macos-13 - if: ${{ github.event_name == 'workflow_dispatch' || github.event.schedule == '0 04 1 * *' || needs.check_latest_from_upstream.outputs.NEW_COMMITS == 'true' }} # runs if started manually, or if scheduled on the first each month, or if new commits were found + permissions: + contents: write + if: | # 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 + github.event_name == 'workflow_dispatch' || + (needs.check_alive_and_permissions.outputs.WORKFLOW_PERMISSION == 'true' && + (vars.SCHEDULED_BUILD != 'false' && github.event.schedule == '0 8 1 * 6') || + (vars.SCHEDULED_SYNC != 'false' && needs.check_latest_from_upstream.outputs.NEW_COMMITS == 'true' ) + ) steps: - name: Select Xcode version run: "sudo xcode-select --switch /Applications/Xcode_14.3.1.app/Contents/Developer" - name: Checkout Repo for syncing + if: | + needs.check_alive_and_permissions.outputs.WORKFLOW_PERMISSION == 'true' && + vars.SCHEDULED_SYNC != 'false' uses: actions/checkout@v3 with: token: ${{ secrets.GH_PAT }} ref: ${{ env.TARGET_BRANCH }} - name: Sync upstream changes - if: ${{ env.SYNC_UPSTREAM != 'false' && github.repository_owner != 'LoopKit' }} # do not run the upstream sync action on the upstream repository + if: | # do not run the upstream sync action on the upstream repository + needs.check_alive_and_permissions.outputs.WORKFLOW_PERMISSION == 'true' && + vars.SCHEDULED_SYNC != 'false' && github.repository_owner != 'LoopKit' id: sync uses: aormsby/Fork-Sync-With-Upstream-action@v3.4 with: @@ -90,14 +194,21 @@ jobs: # Display a sample message based on the sync output var 'has_new_commits' - name: New commits found - if: steps.sync.outputs.has_new_commits == 'true' + if: | + needs.check_alive_and_permissions.outputs.WORKFLOW_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.sync.outputs.has_new_commits == 'false' + if: | + needs.check_alive_and_permissions.outputs.WORKFLOW_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: | + needs.check_alive_and_permissions.outputs.WORKFLOW_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 diff --git a/fastlane/testflight.md b/fastlane/testflight.md index e763353d3..46f1444b7 100644 --- a/fastlane/testflight.md +++ b/fastlane/testflight.md @@ -7,7 +7,24 @@ These instructions allow you to build Loop without having access to a Mac. * You can install Loop on your phone using only the TestFlight app if a phone was lost or the app is accidentally deleted * You do not need to worry about specific Xcode/Mac versions for a given iOS -The setup steps are somewhat involved, but nearly all are one time steps. Subsequent builds are trivial. Your app must be updated once every 90 days, but it's a simple click to make a new build and can be done from anywhere. The 90-day update is a TestFlight requirement, which can be automated. +## **Automatic Builds** +> +> This new version of the browser build **defaults to** automatically updating and building a new version of Loop according to this schedule: +> - automatically checks for updates weekly on Wednesdays and if updates are found, it will build a new version of the app +> - automatically builds once a month regardless of whether there are updates on the first Saturday of the month +> - with each scheduled run (weekly or monthly), a successful Build Loop log appears - if the time is very short, it did not need to build - only the long actions (>20 minutes) built a new Loop app +> +> It also creates an alive branch, if you don't already have one. See [Why do I have an alive branch?](#why-do-i-have-an-alive-branch). +> +> The [**Optional**](#optional) section provides instructions to modify the default behavior if desired. + +> **Repeat Builders** +> - to enable automatic build, your `GH_PAT` token must have `workflow` scope +> - if you previously configured your `GH_PAT` without that scope, see [`GH_PAT` `workflow` permission](#gh_pat-workflow-permission) + +## Introduction + +The setup steps are somewhat involved, but nearly all are one time steps. Subsequent builds are trivial. Your app must be updated once every 90 days, but it's a simple click to make a new build and can be done from anywhere. The 90-day update is a TestFlight requirement, and with this version of Loop, the build process (once you've successfully built once) is automated to update and build at least once a month. There are more detailed instructions in LoopDocs for using GitHub for Browser Builds of Loop, including troubleshooting and build errors. Please refer to: @@ -49,7 +66,7 @@ Log into your GitHub account to create a personal access token; this is one of t 1. Create a [new personal access token](https://github.com/settings/tokens/new): * Enter a name for your token, use "FastLane Access Token". * Change the Expiration selection to `No expiration`. - * Select the `repo` and `workflow` permission scopes. + * Select the `workflow` permission scope - this also selects `repo` scope. * Click "Generate token". * Copy the token and record it. It will be used below as `GH_PAT`. @@ -116,7 +133,7 @@ Note 2 - Depending on your build history, you may find some of the Identifiers a * Loop * Loop Intent Extension * Loop Status Extension - * Small Status Widget + * Loop Widget Extension 1. Click on the identifier's name. 1. On the "App Groups" capabilies, click on the "Configure" button. 1. Select the "Loop App Group" @@ -132,7 +149,7 @@ Note 2 - Depending on your build history, you may find some of the Identifiers a | Loop | com.TEAMID.loopkit.Loop | | Loop Intent Extension | com.TEAMID.loopkit.Loop.Loop-Intent-Extension | | Loop Status Extension | com.TEAMID.loopkit.Loop.statuswidget | -| Small Status Widget | com.TEAMID.loopkit.Loop.SmallStatusWidget | +| Loop Widget Extension | com.TEAMID.loopkit.Loop.LoopWidgetExtension | | WatchApp | com.TEAMID.loopkit.Loop.LoopWatch | | WatchAppExtension | com.TEAMID.loopkit.Loop.LoopWatch.watchkitextension | @@ -152,27 +169,13 @@ If you have created a Loop app in App Store Connect before, you can skip this se You do not need to fill out the next form. That is for submitting to the app store. -## Create Building Certficates +## Create Building Certificates 1. Go back to the "Actions" tab of your LoopWorkspace repository in GitHub. 1. On the left side, select "3. Create Certificates". 1. On the right side, click "Run Workflow", and tap the green `Run workflow` button. 1. Wait, and within a minute or two you should see a green checkmark indicating the workflow succeeded. -## Create a branch named "alive" - -TestFlight builds expire after 90 days. This process you are implementing here will update and rebuild Loop periodically, and requires that you create a branch named "alive" so that GitHub will not inactivate the scheduled rebuild if no code updates are made. - -The "alive" branch will only receive some additional commits to its history, and is not used for building the app. - -1. Go to the "Code" tab of your LoopWorkspace repository. -1. Click the branch selection dropdown button, and then `View all branches`. -1. Click the green "New branch" button (upper right). -1. Type `alive` in the "New branch name" field. -1. Select `LoopKit/LoopWorkspace` as "Source". -1. Select `dev` in the branch dropdown. -1. Click the green "Create new branch" button. - ## Build Loop 1. Click on the "Actions" tab of your LoopWorkspace repository. @@ -187,3 +190,80 @@ The "alive" branch will only receive some additional commits to its history, and ## TestFlight and Deployment Details Please refer to [LoopDocs: Set Up Users](https://loopkit.github.io/loopdocs/gh-actions/gh-first-time/#set-up-users-and-access-testflight) and [LoopDocs: Deploy](https://loopkit.github.io/loopdocs/gh-actions/gh-deploy/) + +## Automatic Build FAQs + +### Why do I have an `alive` branch? + +If a GitHub repository has no activity (no commits are made) in 60 days, then GitHub disables the ability to use automated actions for that repository. We need to take action more frequently than that or the automated build process won't work. + +The updated `build_loop.yml` file uses a special branch called `alive` and adds a dummy commit to the `alive` branch at regular intervals. This "trick" keeps the Actions enabled so the automated build works. + +The branch `alive` is created automatically for you. Do not delete or rename it! Do not modify `alive` yourself; it is not used for building the app. + +## OPTIONAL + +What if you don't want to allow automated updates of the repository or automatic builds? + +You can affect the default behavior: + +1. [`GH_PAT` `workflow` permission](#gh_pat-workflow-permission) +1. [Modify scheduled building and synchronization](#modify-scheduled-building-and-synchronization) + +### `GH_PAT` `workflow` permission + +To enable the scheduled build and sync, the `GH_PAT` must hold the `workflow` permission scopes. This permission serves as the enabler for automatic and scheduled builds with browser build. To verify your token holds this permission, follow these steps. + +1. Go to your [FastLane Access Token](https://github.com/settings/tokens) +2. It should say `repo`, `workflow` next to the `FastLane Access Token` link +3. If it does not, click on the link to open the token detail view +4. Click to check the `workflow` box. You will see that the checked boxes for the `repo` scope become disabled (change color to dark gray and are not clickable) +5. Scroll all the way down to and click the green `Update token` button +6. Your token now holds both required permissions + +If you choose not to have automatic building enabled, be sure the `GH_PAT` has `repo` scope or you won't be able to manually build. + +### Modify scheduled building and synchronization + +You can modify the automation by creating and using some variables. + +To configure the automated build more granularly involves creating up to two environment variables: `SCHEDULED_BUILD` and/or `SCHEDULED_SYNC`. See [How to configure a variable](#how-to-configure-a-variable). + +Note that the weekly and monthly Build Loop actions will continue, but the actions are modified if one or more of these variables is set to false. **A successful Action Log will still appear, even if no automatic activity happens**. + +* If you want to manually decide when to update your repository to the latest commit, but you want the monthly builds and keep-alive to continue: set `SCHEDULED_SYNC` to false and either do not create `SCHEDULED_BUILD` or set it to true +* If you want to only build when an update has been found: set `SCHEDULED_BUILD` to false and either do not create `SCHEDULED_SYNC` or set it to true + * **Warning**: if no updates to your default branch are detected within 90 days, your previous TestFlight build may expire requiring a manual build + +|`SCHEDULED_SYNC`|`SCHEDULED_BUILD`|Automatic Actions| +|---|---|---| +| `true` (or NA) | `true` (or NA) | keep-alive, weekly update check (auto update/build), monthly build with auto update| +| `true` (or NA) | `false` | keep-alive, weekly update check with auto update, only builds if update detected| +| `false` | `true` (or NA) | keep-alive, monthly build, no auto update | +| `false` | `false` | no automatic activity, no keep-alive| + +### How to configure a variable + +1. Go to the "Settings" tab of your LoopWorkspace repository. +2. Click on `Secrets and Variables`. +3. Click on `Actions` +4. You will now see a page titled *Actions secrets and variables*. Click on the `Variables` tab +5. To disable ONLY scheduled building, do the following: + - Click on the green `New repository variable` button (upper right) + - Type `SCHEDULED_BUILD` in the "Name" field + - Type `false` in the "Value" field + - Click the green `Add variable` button to save. +7. To disable scheduled syncing, add a variable: + - Click on the green `New repository variable` button (upper right) + - - Type `SCHEDULED_SYNC` in the "Name" field + - Type `false` in the "Value" field + - Click the green `Add variable` button to save + +Your build will run on the following conditions: +- Default behaviour: + - Run weekly, every Wednesday at 08:00 UTC to check for changes; if there are changes, it will update your repository and build + - Run monthly, every first Saturday of the month at 08:00 UTC, if there are changes, it will update your repository; regardless of changes, it will build + - Each time the action runs, it makes a keep-alive commit to the `alive` branch if necessary +- If you disable any automation (both variables set to `false`), no updates, keep-alive or building happens when Build Loop runs +- If you disabled just scheduled synchronization (`SCHEDULED_SYNC` set to`false`), it will only run once a month, on the first Saturday of the month, no update will happen; keep-alive will run +- If you disabled just scheduled build (`SCHEDULED_BUILD` set to`false`), it will run once weekly, every Wednesday, to check for changes; if there are changes, it will update and build; keep-alive will run