diff --git a/.circleci/config.yml b/.circleci/config.yml index 2bbb6ab20..0ea0edd9a 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,7 +1,7 @@ jobs: - build: + build_and_test: macos: - xcode: 14.1.0 + xcode: 15.4.0 steps: - checkout - run: @@ -9,8 +9,12 @@ jobs: command: git submodule update --init --recursive --depth 1 - run: name: Build Loop - command: set -o pipefail && time xcodebuild -workspace LoopWorkspace.xcworkspace -scheme 'LoopWorkspace' -destination 'platform=iOS Simulator,name=iPhone 13,OS=15.5' build | xcpretty + command: set -o pipefail && time xcodebuild -workspace LoopWorkspace.xcworkspace -scheme 'LoopWorkspace' -destination 'platform=iOS Simulator,name=iPhone 15,OS=17.5' build | xcpretty - run: name: Run Tests - command: set -o pipefail && time xcodebuild -workspace LoopWorkspace.xcworkspace -scheme 'LoopWorkspace' -destination 'platform=iOS Simulator,name=iPhone 13,OS=15.5' test | xcpretty - + command: set -o pipefail && time xcodebuild -workspace LoopWorkspace.xcworkspace -scheme 'LoopWorkspace' -destination 'platform=iOS Simulator,name=iPhone 15,OS=17.5' test | xcpretty +workflows: + version: 2 + build_and_test: + jobs: + - build_and_test diff --git a/.github/workflows/add_identifiers.yml b/.github/workflows/add_identifiers.yml index fd8e871da..aab334ab6 100644 --- a/.github/workflows/add_identifiers.yml +++ b/.github/workflows/add_identifiers.yml @@ -1,24 +1,34 @@ name: 2. Add Identifiers -run-name: Add Identifiers +run-name: Add Identifiers (${{ github.ref_name }}) on: workflow_dispatch: jobs: - secrets: + validate: + name: Validate uses: ./.github/workflows/validate_secrets.yml secrets: inherit - + identifiers: - needs: secrets + name: Add Identifiers + needs: validate runs-on: macos-14 steps: + # Uncomment to manually select latest Xcode if needed + #- name: Select Latest Xcode + # run: "sudo xcode-select --switch /Applications/Xcode_13.0.app/Contents/Developer" + # Checks-out the repo - name: Checkout Repo uses: actions/checkout@v4 - + # Patch Fastlane Match to not print tables - name: Patch Match Tables run: find /usr/local/lib/ruby/gems -name table_printer.rb | xargs sed -i "" "/puts(Terminal::Table.new(params))/d" + + # 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 @@ -26,7 +36,7 @@ jobs: # Create or update identifiers for app - name: Fastlane Provision - run: fastlane identifiers + run: bundle exec fastlane identifiers env: TEAMID: ${{ secrets.TEAMID }} GH_PAT: ${{ secrets.GH_PAT }} diff --git a/.github/workflows/build_loop.yml b/.github/workflows/build_loop.yml index 697c291b5..dc84dbee8 100644 --- a/.github/workflows/build_loop.yml +++ b/.github/workflows/build_loop.yml @@ -1,45 +1,267 @@ name: 4. Build Loop -run-name: Build Loop ${{ github.ref_name }} +run-name: Build Loop (${{ github.ref_name }}) on: workflow_dispatch: ## Remove the "#" sign from the beginning of the line below to get automated builds on push (code changes in your repository) #push: + + schedule: + - cron: '0 8 * * 3' # Checks for updates at 08:00 UTC every Wednesday + - cron: '0 6 1 * *' # Builds the app on the 1st of every month at 06:00 UTC - ## Remove the "#" sign from the beginning of the two lines below to get automated builds every two months - #schedule: - #- cron: '0 17 1 */2 *' # Runs at 17:00 UTC on the 1st in Jan, Mar, May, Jul, Sep and Nov. +env: + 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, and target branch on upstream to be kept alive (replace with specific branch name if needed) + ALIVE_BRANCH: alive jobs: - secrets: + validate: + name: Validate 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: validate + 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.GH_PAT }} + 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 == 'false' + env: + GITHUB_TOKEN: ${{ secrets.GH_PAT }} + 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: [validate, 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@v4 + with: + token: ${{ secrets.GH_PAT }} + ref: alive + + - name: Sync upstream changes + 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.1 + with: + target_sync_branch: ${{ env.ALIVE_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: | + 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: | + 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: - needs: secrets + name: Build + needs: [validate, check_alive_and_permissions, check_latest_from_upstream] runs-on: macos-14 + 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 6 1 * *') || + (vars.SCHEDULED_SYNC != 'false' && needs.check_latest_from_upstream.outputs.NEW_COMMITS == 'true' ) + ) steps: - # Uncomment to manually select latest Xcode if needed - - name: Select Latest Xcode - run: "sudo xcode-select --switch /Applications/Xcode_15.3.app/Contents/Developer" + - name: Select Xcode version + run: "sudo xcode-select --switch /Applications/Xcode_15.4.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@v4 + with: + token: ${{ secrets.GH_PAT }} + ref: ${{ env.TARGET_BRANCH }} + + - name: Sync upstream changes + 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.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: | + 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: | + 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 - # Checks-out the repo - - name: Checkout Repo + - 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: find /usr/local/lib/ruby/gems -name table_printer.rb | xargs sed -i "" "/puts(Terminal::Table.new(params))/d" + # 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: fastlane build_loop + run: bundle exec fastlane build_loop env: TEAMID: ${{ secrets.TEAMID }} GH_PAT: ${{ secrets.GH_PAT }} @@ -50,7 +272,7 @@ jobs: # Upload to TestFlight - name: Fastlane upload to TestFlight - run: fastlane release + run: bundle exec fastlane release env: TEAMID: ${{ secrets.TEAMID }} GH_PAT: ${{ secrets.GH_PAT }} @@ -59,8 +281,9 @@ jobs: FASTLANE_KEY: ${{ secrets.FASTLANE_KEY }} MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }} - # Upload IPA and Symbols - - name: Upload IPA and Symbol artifacts + # Upload Build artifacts + - name: Upload build log, IPA and Symbol artifacts + if: always() uses: actions/upload-artifact@v4 with: name: build-artifacts diff --git a/.github/workflows/create_certs.yml b/.github/workflows/create_certs.yml index f41170155..90bb4f75b 100644 --- a/.github/workflows/create_certs.yml +++ b/.github/workflows/create_certs.yml @@ -1,24 +1,34 @@ name: 3. Create Certificates -run-name: Create Certificates +run-name: Create Certificates (${{ github.ref_name }}) on: workflow_dispatch: jobs: - secrets: + validate: + name: Validate uses: ./.github/workflows/validate_secrets.yml secrets: inherit - + certificates: - needs: secrets + name: Create Certificates + needs: validate runs-on: macos-14 steps: + # Uncomment to manually select latest Xcode if needed + #- name: Select Latest Xcode + # run: "sudo xcode-select --switch /Applications/Xcode_13.0.app/Contents/Developer" + # Checks-out the repo - name: Checkout Repo uses: actions/checkout@v4 - + # Patch Fastlane Match to not print tables - name: Patch Match Tables run: find /usr/local/lib/ruby/gems -name table_printer.rb | xargs sed -i "" "/puts(Terminal::Table.new(params))/d" + + # 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 @@ -26,7 +36,7 @@ jobs: # Create or update certificates for app - name: Create Certificates - run: fastlane certs + run: bundle exec fastlane certs env: TEAMID: ${{ secrets.TEAMID }} GH_PAT: ${{ secrets.GH_PAT }} diff --git a/.github/workflows/validate_secrets.yml b/.github/workflows/validate_secrets.yml index f098db0cf..5ad976a01 100644 --- a/.github/workflows/validate_secrets.yml +++ b/.github/workflows/validate_secrets.yml @@ -1,74 +1,197 @@ name: 1. Validate Secrets -run-name: Validate Secrets +run-name: Validate Secrets (${{ github.ref_name }}) on: [workflow_call, workflow_dispatch] - + jobs: - validate: + validate-access-token: + name: Access runs-on: macos-14 + env: + GH_PAT: ${{ secrets.GH_PAT }} + GH_TOKEN: ${{ secrets.GH_PAT }} + outputs: + HAS_WORKFLOW_PERMISSION: ${{ steps.access-token.outputs.has_workflow_permission }} + steps: + - name: Validate Access Token + id: access-token + 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_workflow_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_workflow_permission=true" >> $GITHUB_OUTPUT + fi + fi + + # Exit unsuccessfully if secret validation failed. + if [ $failed ]; then + exit 2 + fi + + validate-match-secrets: + name: Match-Secrets + needs: validate-access-token + runs-on: macos-14 + env: + GH_TOKEN: ${{ secrets.GH_PAT }} + steps: + - name: Validate Match-Secrets + run: | + # Validate Match-Secrets + + # Ensure that gh exit codes are handled when output is piped. + set -o pipefail + + # If a Match-Secrets repository does not exist, attempt to create one. + if ! visibility=$(gh repo view ${{ github.repository_owner }}/Match-Secrets --json visibility | jq --raw-output '.visibility | ascii_downcase'); then + echo "A '${{ github.repository_owner }}/Match-Secrets' repository could not be found using the GH_PAT secret. Attempting to create one..." + + # Create a private Match-Secrets repository and verify that it exists and that it is private. + if gh repo create ${{ github.repository_owner }}/Match-Secrets --private >/dev/null && [ "$(gh repo view ${{ github.repository_owner }}/Match-Secrets --json visibility | jq --raw-output '.visibility | ascii_downcase')" == "private" ]; then + echo "Created a private '${{ github.repository_owner }}/Match-Secrets' repository." + else + failed=true + echo "::error::Unable to create a private '${{ github.repository_owner }}/Match-Secrets' repository. Create a private 'Match-Secrets' repository manually and try again. If a private 'Match-Secrets' repository already exists, verify that the token permissions of the GH_PAT are set correctly (or update them) at https://github.com/settings/tokens and try again." + fi + # Otherwise, if a Match-Secrets repository exists, but it is public, cause validation to fail. + elif [[ "$visibility" == "public" ]]; then + failed=true + echo "::error::A '${{ github.repository_owner }}/Match-Secrets' repository was found, but it is public. Change the repository visibility to private (or delete it) and try again. If necessary, a private repository will be created for you." + else + echo "Found a private '${{ github.repository_owner }}/Match-Secrets' repository to use." + fi + + # Exit unsuccessfully if secret validation failed. + if [ $failed ]; then + exit 2 + fi + + validate-fastlane-secrets: + name: Fastlane + needs: [validate-access-token, validate-match-secrets] + runs-on: macos-14 + env: + GH_PAT: ${{ secrets.GH_PAT }} + GH_TOKEN: ${{ secrets.GH_PAT }} + FASTLANE_ISSUER_ID: ${{ secrets.FASTLANE_ISSUER_ID }} + FASTLANE_KEY_ID: ${{ secrets.FASTLANE_KEY_ID }} + FASTLANE_KEY: ${{ secrets.FASTLANE_KEY }} + MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }} + TEAMID: ${{ secrets.TEAMID }} steps: - # Checks-out the repo - name: Checkout Repo uses: actions/checkout@v4 + - 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 - # Validates the repo secrets - - name: Validate Secrets + - name: Validate Fastlane Secrets run: | - # Validate Secrets - echo Validating Repository Secrets... + # Validate Fastlane Secrets # Validate TEAMID if [ -z "$TEAMID" ]; then failed=true - echo "::error::TEAMID secret is unset or empty. Set it and try again." + echo "::error::The TEAMID secret is unset or empty. Set it and try again." elif [ ${#TEAMID} -ne 10 ]; then failed=true - echo "::error::TEAMID secret is set but has wrong length. Verify that it is set correctly and try again." + echo "::error::The TEAMID secret is set but has wrong length. Verify that it is set correctly and try again." + elif ! [[ $TEAMID =~ ^[A-Z0-9]+$ ]]; then + failed=true + echo "::error::The TEAMID secret is set but invalid. Verify that it is set correctly (only uppercase letters and numbers) and try again." fi - # Validate GH_PAT - if [ -z "$GH_PAT" ]; then - failed=true - echo "::error::GH_PAT secret is unset or empty. Set it and try again." - elif [ "$(gh api -H "Accept: application/vnd.github+json" /repos/${{ github.repository_owner }}/Match-Secrets | jq --raw-output '.permissions.push')" != "true" ]; then + # Validate MATCH_PASSWORD + if [ -z "$MATCH_PASSWORD" ]; then failed=true - echo "::error::GH_PAT secret is set but invalid or lacking appropriate privileges on the ${{ github.repository_owner }}/Match-Secrets repository. Verify that it is set correctly and try again." + echo "::error::The MATCH_PASSWORD secret is unset or empty. Set it and try again." fi + # Ensure that fastlane exit codes are handled when output is piped. + set -o pipefail + # Validate FASTLANE_ISSUER_ID, FASTLANE_KEY_ID, and FASTLANE_KEY + FASTLANE_KEY_ID_PATTERN='^[A-Z0-9]+$' + FASTLANE_ISSUER_ID_PATTERN='^\{?[A-F0-9a-f]{8}-[A-F0-9a-f]{4}-[A-F0-9a-f]{4}-[A-F0-9a-f]{4}-[A-F0-9a-f]{12}\}?$' + if [ -z "$FASTLANE_ISSUER_ID" ] || [ -z "$FASTLANE_KEY_ID" ] || [ -z "$FASTLANE_KEY" ]; then failed=true [ -z "$FASTLANE_ISSUER_ID" ] && echo "::error::The FASTLANE_ISSUER_ID secret is unset or empty. Set it and try again." [ -z "$FASTLANE_KEY_ID" ] && echo "::error::The FASTLANE_KEY_ID secret is unset or empty. Set it and try again." [ -z "$FASTLANE_KEY" ] && echo "::error::The FASTLANE_KEY secret is unset or empty. Set it and try again." - elif ! echo "$FASTLANE_KEY" | openssl pkcs8 -nocrypt >/dev/null; then + elif [ ${#FASTLANE_KEY_ID} -ne 10 ]; then failed=true - echo "::error::The FASTLANE_KEY secret is set but invalid. Verify that it is set correctly and try again." - elif ! fastlane validate_secrets; then + echo "::error::The FASTLANE_KEY_ID secret is set but has wrong length. Verify that you copied it correctly from the 'Keys' tab at https://appstoreconnect.apple.com/access/api and try again." + elif ! [[ $FASTLANE_KEY_ID =~ $FASTLANE_KEY_ID_PATTERN ]]; then failed=true - echo "::error::Unable to create a valid authorization token for the App Store Connect API.\ - Verify that the FASTLANE_ISSUER_ID, FASTLANE_KEY_ID, and FASTLANE_KEY secrets are set correctly and try again." - fi - - # Validate MATCH_PASSWORD - if [ -z "$MATCH_PASSWORD" ]; then + echo "::error::The FASTLANE_KEY_ID secret is set but invalid. Verify that you copied it correctly from the 'Keys' tab at https://appstoreconnect.apple.com/access/api and try again." + elif ! [[ $FASTLANE_ISSUER_ID =~ $FASTLANE_ISSUER_ID_PATTERN ]]; then failed=true - echo "::error::The MATCH_PASSWORD secret is unset or empty. Set it and try again." + echo "::error::The FASTLANE_ISSUER_ID secret is set but invalid. Verify that you copied it correctly from the 'Keys' tab at https://appstoreconnect.apple.com/access/api and try again." + elif ! echo "$FASTLANE_KEY" | openssl pkcs8 -nocrypt >/dev/null; then + failed=true + echo "::error::The FASTLANE_KEY secret is set but invalid. Verify that you copied it correctly from the API Key file (*.p8) you downloaded and try again." + elif ! bundle exec fastlane validate_secrets 2>&1 | tee fastlane.log; then + if grep -q "bad decrypt" fastlane.log; then + failed=true + echo "::error::Unable to decrypt the Match-Secrets repository using the MATCH_PASSWORD secret. Verify that it is set correctly and try again." + elif grep -q -e "required agreement" -e "license agreement" fastlane.log; then + failed=true + echo "::error::Unable to create a valid authorization token for the App Store Connect API. Verify that the latest developer program license agreement has been accepted at https://developer.apple.com/account (review and accept any updated agreement), then wait a few minutes for changes to propagate and try again." + elif ! grep -q -e "No code signing identity found" -e "Could not install WWDR certificate" fastlane.log; then + failed=true + echo "::error::Unable to create a valid authorization token for the App Store Connect API. Verify that the FASTLANE_ISSUER_ID, FASTLANE_KEY_ID, and FASTLANE_KEY secrets are set correctly and try again." + fi fi # Exit unsuccessfully if secret validation failed. if [ $failed ]; then exit 2 fi - shell: bash - env: - TEAMID: ${{ secrets.TEAMID }} - GH_PAT: ${{ secrets.GH_PAT }} - FASTLANE_ISSUER_ID: ${{ secrets.FASTLANE_ISSUER_ID }} - FASTLANE_KEY_ID: ${{ secrets.FASTLANE_KEY_ID }} - FASTLANE_KEY: ${{ secrets.FASTLANE_KEY }} - MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }} - GH_TOKEN: ${{ secrets.GH_PAT }} diff --git a/.gitmodules b/.gitmodules index 5df7f69a9..1308e60c0 100644 --- a/.gitmodules +++ b/.gitmodules @@ -10,15 +10,12 @@ [submodule "dexcom-share-client-swift"] path = dexcom-share-client-swift url = https://github.com/LoopKit/dexcom-share-client-swift.git -[submodule "rileylink_ios"] - path = rileylink_ios - url = https://github.com/ps2/rileylink_ios.git -[submodule "MKRingProgressView"] - path = MKRingProgressView - url = https://github.com/LoopKit/MKRingProgressView.git +[submodule "RileyLinkKit"] + path = RileyLinkKit + url = https://github.com/LoopKit/RileyLinkKit [submodule "NightscoutService"] path = NightscoutService - url = https://github.com/ps2/NightscoutService.git + url = https://github.com/LoopKit/NightscoutService.git [submodule "Minizip"] path = Minizip url = https://github.com/LoopKit/Minizip.git @@ -37,9 +34,9 @@ [submodule "OmniBLE"] path = OmniBLE url = https://github.com/LoopKit/OmniBLE.git -[submodule "NightscoutAPIClient"] - path = NightscoutAPIClient - url = https://github.com/LoopKit/NightscoutAPIClient.git +[submodule "NightscoutRemoteCGM"] + path = NightscoutRemoteCGM + url = https://github.com/LoopKit/NightscoutRemoteCGM.git [submodule "LoopSupport"] path = LoopSupport url = https://github.com/LoopKit/LoopSupport @@ -49,12 +46,15 @@ [submodule "TidepoolService"] path = TidepoolService url = https://github.com/LoopKit/TidepoolService.git -[submodule "TidepoolKit"] - path = TidepoolKit - url = https://github.com/LoopKit/TidepoolKit.git [submodule "OmniKit"] path = OmniKit url = https://github.com/LoopKit/OmniKit.git [submodule "MinimedKit"] path = MinimedKit url = https://github.com/LoopKit/MinimedKit.git +[submodule "MixpanelService"] + path = MixpanelService + url = https://github.com/LoopKit/MixpanelService +[submodule "LibreTransmitter"] + path = LibreTransmitter + url = https://github.com/LoopKit/LibreTransmitter.git diff --git a/AmplitudeService b/AmplitudeService index 5c77f2099..98f9759b1 160000 --- a/AmplitudeService +++ b/AmplitudeService @@ -1 +1 @@ -Subproject commit 5c77f20995eee141e00141065eff3579db430d9c +Subproject commit 98f9759b17e8716c65257c24a652564ead3c5183 diff --git a/CGMBLEKit b/CGMBLEKit index 5a4beb4cd..905cc0a58 160000 --- a/CGMBLEKit +++ b/CGMBLEKit @@ -1 +1 @@ -Subproject commit 5a4beb4cd89699cfae2d93938659cc8414e16373 +Subproject commit 905cc0a58509e417aa7bb2ef9dacfb36f009231c diff --git a/G7SensorKit b/G7SensorKit index 14205c1f1..b5e992e21 160000 --- a/G7SensorKit +++ b/G7SensorKit @@ -1 +1 @@ -Subproject commit 14205c1f1f16ecc3434d6c7f3f349a76176cb7f2 +Subproject commit b5e992e211d2ac6224acb105dd97fb484767da72 diff --git a/Gemfile.lock b/Gemfile.lock index ece7a3ae2..1ff12d548 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,52 +1,56 @@ GEM remote: https://rubygems.org/ specs: - CFPropertyList (3.0.4) + CFPropertyList (3.0.7) + base64 + nkf rexml - addressable (2.8.0) - public_suffix (>= 2.0.2, < 5.0) - artifactory (3.0.15) + addressable (2.8.7) + public_suffix (>= 2.0.2, < 7.0) + artifactory (3.0.17) atomos (0.1.3) - aws-eventstream (1.2.0) - aws-partitions (1.516.0) - aws-sdk-core (3.121.2) - aws-eventstream (~> 1, >= 1.0.2) - aws-partitions (~> 1, >= 1.239.0) - aws-sigv4 (~> 1.1) - jmespath (~> 1.0) - aws-sdk-kms (1.50.0) - aws-sdk-core (~> 3, >= 3.121.2) + aws-eventstream (1.3.0) + aws-partitions (1.949.0) + aws-sdk-core (3.200.0) + aws-eventstream (~> 1, >= 1.3.0) + aws-partitions (~> 1, >= 1.651.0) + aws-sigv4 (~> 1.8) + jmespath (~> 1, >= 1.6.1) + aws-sdk-kms (1.87.0) + aws-sdk-core (~> 3, >= 3.199.0) aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.104.0) - aws-sdk-core (~> 3, >= 3.121.2) + aws-sdk-s3 (1.155.0) + aws-sdk-core (~> 3, >= 3.199.0) aws-sdk-kms (~> 1) - aws-sigv4 (~> 1.4) - aws-sigv4 (1.4.0) + aws-sigv4 (~> 1.8) + aws-sigv4 (1.8.0) aws-eventstream (~> 1, >= 1.0.2) babosa (1.0.4) - claide (1.0.3) + base64 (0.2.0) + claide (1.1.0) colored (1.2) colored2 (3.1.2) commander (4.6.0) highline (~> 2.0.0) declarative (0.0.20) - digest-crc (0.6.4) + digest-crc (0.6.5) rake (>= 12.0.0, < 14.0.0) domain_name (0.5.20190701) unf (>= 0.0.5, < 1.0.0) - dotenv (2.7.6) + dotenv (2.8.1) emoji_regex (3.2.3) - excon (0.87.0) - faraday (1.8.0) + excon (0.109.0) + faraday (1.10.3) faraday-em_http (~> 1.0) faraday-em_synchrony (~> 1.0) faraday-excon (~> 1.1) - faraday-httpclient (~> 1.0.1) + faraday-httpclient (~> 1.0) + faraday-multipart (~> 1.0) faraday-net_http (~> 1.0) - faraday-net_http_persistent (~> 1.1) + faraday-net_http_persistent (~> 1.0) faraday-patron (~> 1.0) faraday-rack (~> 1.0) - multipart-post (>= 1.2, < 3) + faraday-retry (~> 1.0) ruby2_keywords (>= 0.0.4) faraday-cookie_jar (0.0.7) faraday (>= 0.8.0) @@ -55,21 +59,24 @@ GEM faraday-em_synchrony (1.0.0) faraday-excon (1.1.0) faraday-httpclient (1.0.1) + faraday-multipart (1.0.4) + multipart-post (~> 2) faraday-net_http (1.0.1) faraday-net_http_persistent (1.2.0) faraday-patron (1.0.0) faraday-rack (1.0.0) + faraday-retry (1.0.3) faraday_middleware (1.2.0) faraday (~> 1.0) - fastimage (2.2.5) - fastlane (2.196.0) + fastimage (2.3.1) + fastlane (2.221.1) CFPropertyList (>= 2.3, < 4.0.0) addressable (>= 2.8, < 3.0.0) artifactory (~> 3.0) aws-sdk-s3 (~> 1.0) babosa (>= 1.0.3, < 2.0.0) bundler (>= 1.12.0, < 3.0.0) - colored + colored (~> 1.2) commander (~> 4.6) dotenv (>= 2.1.1, < 3.0.0) emoji_regex (>= 0.1, < 4.0) @@ -81,30 +88,32 @@ GEM gh_inspector (>= 1.1.2, < 2.0.0) google-apis-androidpublisher_v3 (~> 0.3) google-apis-playcustomapp_v1 (~> 0.1) + google-cloud-env (>= 1.6.0, < 2.0.0) google-cloud-storage (~> 1.31) highline (~> 2.0) + http-cookie (~> 1.0.5) json (< 3.0.0) jwt (>= 2.1.0, < 3) mini_magick (>= 4.9.4, < 5.0.0) - multipart-post (~> 2.0.0) + multipart-post (>= 2.0.0, < 3.0.0) naturally (~> 2.2) - optparse (~> 0.1.1) + optparse (>= 0.1.1, < 1.0.0) plist (>= 3.1.0, < 4.0.0) rubyzip (>= 2.0.0, < 3.0.0) - security (= 0.1.3) + security (= 0.1.5) simctl (~> 1.6.3) terminal-notifier (>= 2.0.0, < 3.0.0) - terminal-table (>= 1.4.5, < 2.0.0) + terminal-table (~> 3) tty-screen (>= 0.6.3, < 1.0.0) tty-spinner (>= 0.8.0, < 1.0.0) word_wrap (~> 1.0.0) xcodeproj (>= 1.13.0, < 2.0.0) xcpretty (~> 0.3.0) - xcpretty-travis-formatter (>= 0.0.3) + xcpretty-travis-formatter (>= 0.0.3, < 2.0.0) gh_inspector (1.1.3) - google-apis-androidpublisher_v3 (0.12.0) - google-apis-core (>= 0.4, < 2.a) - google-apis-core (0.4.1) + google-apis-androidpublisher_v3 (0.54.0) + google-apis-core (>= 0.11.0, < 2.a) + google-apis-core (0.11.3) addressable (~> 2.5, >= 2.5.1) googleauth (>= 0.16.2, < 2.a) httpclient (>= 2.8.1, < 3.a) @@ -112,87 +121,87 @@ GEM representable (~> 3.0) retriable (>= 2.0, < 4.a) rexml - webrick - google-apis-iamcredentials_v1 (0.7.0) - google-apis-core (>= 0.4, < 2.a) - google-apis-playcustomapp_v1 (0.5.0) - google-apis-core (>= 0.4, < 2.a) - google-apis-storage_v1 (0.8.0) - google-apis-core (>= 0.4, < 2.a) - google-cloud-core (1.6.0) - google-cloud-env (~> 1.0) + google-apis-iamcredentials_v1 (0.17.0) + google-apis-core (>= 0.11.0, < 2.a) + google-apis-playcustomapp_v1 (0.13.0) + google-apis-core (>= 0.11.0, < 2.a) + google-apis-storage_v1 (0.29.0) + google-apis-core (>= 0.11.0, < 2.a) + google-cloud-core (1.6.1) + google-cloud-env (>= 1.0, < 3.a) google-cloud-errors (~> 1.0) - google-cloud-env (1.5.0) - faraday (>= 0.17.3, < 2.0) - google-cloud-errors (1.2.0) - google-cloud-storage (1.34.1) - addressable (~> 2.5) + google-cloud-env (1.6.0) + faraday (>= 0.17.3, < 3.0) + google-cloud-errors (1.3.1) + google-cloud-storage (1.45.0) + addressable (~> 2.8) digest-crc (~> 0.4) google-apis-iamcredentials_v1 (~> 0.1) - google-apis-storage_v1 (~> 0.1) + google-apis-storage_v1 (~> 0.29.0) google-cloud-core (~> 1.6) googleauth (>= 0.16.2, < 2.a) mini_mime (~> 1.0) - googleauth (1.0.0) - faraday (>= 0.17.3, < 2.0) + googleauth (1.8.1) + faraday (>= 0.17.3, < 3.a) jwt (>= 1.4, < 3.0) - memoist (~> 0.16) multi_json (~> 1.11) os (>= 0.9, < 2.0) signet (>= 0.16, < 2.a) highline (2.0.3) - http-cookie (1.0.4) + http-cookie (1.0.6) domain_name (~> 0.5) httpclient (2.8.3) jmespath (1.6.2) - json (2.6.0) - jwt (2.3.0) - memoist (0.16.2) - mini_magick (4.11.0) - mini_mime (1.1.2) + json (2.7.2) + jwt (2.8.2) + base64 + mini_magick (4.13.1) + mini_mime (1.1.5) multi_json (1.15.0) - multipart-post (2.0.0) + multipart-post (2.4.1) nanaimo (0.3.0) naturally (2.2.1) - optparse (0.1.1) - os (1.1.1) - plist (3.6.0) - public_suffix (4.0.6) - rake (13.0.6) - representable (3.1.1) + nkf (0.2.0) + optparse (0.5.0) + os (1.1.4) + plist (3.7.1) + public_suffix (5.1.1) + rake (13.2.1) + representable (3.2.0) declarative (< 0.1.0) trailblazer-option (>= 0.1.1, < 0.2.0) uber (< 0.2.0) retriable (3.1.2) - rexml (3.2.5) + rexml (3.2.9) + strscan rouge (2.0.7) ruby2_keywords (0.0.5) rubyzip (2.3.2) - security (0.1.3) - signet (0.16.0) + security (0.1.5) + signet (0.18.0) addressable (~> 2.8) - faraday (>= 0.17.3, < 2.0) + faraday (>= 0.17.5, < 3.a) jwt (>= 1.5, < 3.0) multi_json (~> 1.10) - simctl (1.6.8) + simctl (1.6.10) CFPropertyList naturally + strscan (3.1.0) terminal-notifier (2.0.0) - terminal-table (1.8.0) - unicode-display_width (~> 1.1, >= 1.1.1) - trailblazer-option (0.1.1) + terminal-table (3.0.2) + unicode-display_width (>= 1.1.1, < 3) + trailblazer-option (0.1.2) tty-cursor (0.7.1) - tty-screen (0.8.1) + tty-screen (0.8.2) tty-spinner (0.9.3) tty-cursor (~> 0.7) uber (0.1.0) unf (0.1.4) unf_ext - unf_ext (0.0.8) - unicode-display_width (1.8.0) - webrick (1.7.0) + unf_ext (0.0.9.1) + unicode-display_width (2.5.0) word_wrap (1.0.0) - xcodeproj (1.21.0) + xcodeproj (1.24.0) CFPropertyList (>= 2.3.3, < 4.0) atomos (~> 0.1.3) claide (>= 1.0.2, < 2.0) @@ -206,10 +215,13 @@ GEM PLATFORMS arm64-darwin-21 + arm64-darwin-22 + arm64-darwin-23 x86_64-darwin-19 + x86_64-linux DEPENDENCIES fastlane BUNDLED WITH - 2.3.26 + 2.4.19 diff --git a/InfoCustomizations.txt b/InfoCustomizations.txt new file mode 100644 index 000000000..dc3509c64 --- /dev/null +++ b/InfoCustomizations.txt @@ -0,0 +1 @@ +TidepoolServiceClientId=diy-loop diff --git a/LibreTransmitter b/LibreTransmitter new file mode 160000 index 000000000..c01eba63e --- /dev/null +++ b/LibreTransmitter @@ -0,0 +1 @@ +Subproject commit c01eba63e94e9f6f2841a8835680c4e39c61b18d diff --git a/LogglyService b/LogglyService index 9336301bc..1bb822374 160000 --- a/LogglyService +++ b/LogglyService @@ -1 +1 @@ -Subproject commit 9336301bc7d4768c8442c5edd8109441a30cd60d +Subproject commit 1bb8223748003e51770875200489cce83f74c570 diff --git a/Loop b/Loop index c6b058b42..49feb51f0 160000 --- a/Loop +++ b/Loop @@ -1 +1 @@ -Subproject commit c6b058b4276681600979aaeba518c635f06ac135 +Subproject commit 49feb51f07f8df6a384a3b2d1a2c81ef90917194 diff --git a/LoopConfigOverride.xcconfig b/LoopConfigOverride.xcconfig index 3ffab2cb9..2969db288 100644 --- a/LoopConfigOverride.xcconfig +++ b/LoopConfigOverride.xcconfig @@ -10,7 +10,7 @@ //URL_SCHEME_NAME = $(MAIN_APP_DISPLAY_NAME) // Features -SWIFT_ACTIVE_COMPILATION_CONDITIONS = $(inherited) SIMULATORS_ENABLED //DEBUG_FEATURES_ENABLED +SWIFT_ACTIVE_COMPILATION_CONDITIONS = $(inherited) EXPERIMENTAL_FEATURES_ENABLED SIMULATORS_ENABLED ALLOW_ALGORITHM_EXPERIMENTS DEBUG_FEATURES_ENABLED // Put your team id here for signing //LOOP_DEVELOPMENT_TEAM = UY678SP37Q diff --git a/LoopKit b/LoopKit index 9835a29f1..873b3b7c4 160000 --- a/LoopKit +++ b/LoopKit @@ -1 +1 @@ -Subproject commit 9835a29f1bac9f75023f39c376479a2e6a6c8ccd +Subproject commit 873b3b7c406cfc982f9061afb5f5e27e88d9208d diff --git a/LoopOnboarding b/LoopOnboarding index 308097ae9..4c5c19279 160000 --- a/LoopOnboarding +++ b/LoopOnboarding @@ -1 +1 @@ -Subproject commit 308097ae9066b3f1336cfc113c3e85dda329a3a6 +Subproject commit 4c5c1927999ae5dd3861e67ca9f10c14a59d1cc6 diff --git a/LoopSupport b/LoopSupport index 40362a12e..4728c6783 160000 --- a/LoopSupport +++ b/LoopSupport @@ -1 +1 @@ -Subproject commit 40362a12e3e73e40ffadef5a1252d100c5761e8e +Subproject commit 4728c67836e6a60118fcf6cc262010e9b3d14e68 diff --git a/LoopWorkspace.xcworkspace/contents.xcworkspacedata b/LoopWorkspace.xcworkspace/contents.xcworkspacedata index d6870b083..b1d1574f0 100644 --- a/LoopWorkspace.xcworkspace/contents.xcworkspacedata +++ b/LoopWorkspace.xcworkspace/contents.xcworkspacedata @@ -4,6 +4,12 @@ + + + + @@ -14,6 +20,19 @@ + + + + + + + + @@ -32,32 +51,23 @@ - - + location = "group:MixpanelService/MixpanelService.xcodeproj"> + location = "group:LogglyService/LogglyService.xcodeproj"> + location = "group:NightscoutRemoteCGM/NightscoutRemoteCGM.xcodeproj"> - - - - + location = "group:NightscoutService/NightscoutService.xcodeproj"> @@ -68,12 +78,18 @@ + + + + diff --git a/LoopWorkspace.xcworkspace/xcshareddata/swiftpm/Package.resolved b/LoopWorkspace.xcworkspace/xcshareddata/swiftpm/Package.resolved index 8ef5acb7a..20f167d45 100644 --- a/LoopWorkspace.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/LoopWorkspace.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,4 +1,5 @@ { + "originHash" : "f8d1e9c237647ab612da7f2bd3ae26946f39410508314c00cf54509a673f147e", "pins" : [ { "identity" : "amplitude-ios", @@ -6,7 +7,7 @@ "location" : "https://github.com/amplitude/Amplitude-iOS.git", "state" : { "branch" : "main", - "revision" : "c3cb4810a021ccd172caaed33bef5f400899f599" + "revision" : "e818b182f5c3d5ce5035deab90bca108175b3561" } }, { @@ -14,8 +15,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/amplitude/analytics-connector-ios.git", "state" : { - "revision" : "b44981119e2efc0edd1d2e26831c3c4c0e0264e6", - "version" : "1.0.0" + "revision" : "d3d682a26ca6f4947ece2c2e627971bb41b940fa", + "version" : "1.0.1" } }, { @@ -32,8 +33,26 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/krzyzanowskim/CryptoSwift", "state" : { - "revision" : "12f2389aca4a07e0dd54c86ec23d0721ed88b8db", - "version" : "1.4.3" + "revision" : "eee9ad754926c40a0f7e73f152357d37b119b7fa", + "version" : "1.7.1" + } + }, + { + "identity" : "mixpanel-swift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/mixpanel/mixpanel-swift.git", + "state" : { + "branch" : "master", + "revision" : "c676a9737c76e127e3ae5776247b226bc6d7652d" + } + }, + { + "identity" : "mkringprogressview", + "kind" : "remoteSourceControl", + "location" : "https://github.com/maxkonovalov/MKRingProgressView.git", + "state" : { + "branch" : "master", + "revision" : "660888aab1d2ab0ed7eb9eb53caec12af4955fa7" } }, { @@ -42,7 +61,7 @@ "location" : "https://github.com/LoopKit/NightscoutKit", "state" : { "branch" : "main", - "revision" : "54c668c7154eca0ccd7b5714e1d206e488cd1bd9" + "revision" : "ca8e2cea82ab465282cd180ce01d64c1cf25478d" } }, { @@ -53,15 +72,42 @@ "revision" : "8e4022f2852d77240d0a17482cbfe325354aac70" } }, + { + "identity" : "slidebutton", + "kind" : "remoteSourceControl", + "location" : "https://github.com/no-comment/SlideButton", + "state" : { + "branch" : "main", + "revision" : "5eacebba4d7deeb693592bc9a62ab2d2181e133b" + } + }, { "identity" : "swiftcharts", "kind" : "remoteSourceControl", "location" : "https://github.com/ivanschuetz/SwiftCharts", "state" : { "branch" : "master", - "revision" : "3d011f67eccb1ffa622fbfccb1348eed80309ae8" + "revision" : "c354c1945bb35a1f01b665b22474f6db28cba4a2" + } + }, + { + "identity" : "tidepoolkit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/tidepool-org/TidepoolKit", + "state" : { + "branch" : "dev", + "revision" : "54045c2e7d720dcd8a0909037772dcd6f54f0158" + } + }, + { + "identity" : "zipfoundation", + "kind" : "remoteSourceControl", + "location" : "https://github.com/LoopKit/ZIPFoundation.git", + "state" : { + "branch" : "stream-entry", + "revision" : "c67b7509ec82ee2b4b0ab3f97742b94ed9692494" } } ], - "version" : 2 + "version" : 3 } diff --git a/LoopWorkspace.xcworkspace/xcshareddata/xcschemes/LoopWorkspace.xcscheme b/LoopWorkspace.xcworkspace/xcshareddata/xcschemes/LoopWorkspace.xcscheme index 00fa61ad1..f229038dd 100644 --- a/LoopWorkspace.xcworkspace/xcshareddata/xcschemes/LoopWorkspace.xcscheme +++ b/LoopWorkspace.xcworkspace/xcshareddata/xcschemes/LoopWorkspace.xcscheme @@ -1,25 +1,11 @@ - - - - - - - - + + + + - - - - + + + + + + + + + BuildableName = "NightscoutRemoteCGMPlugin.loopplugin" + BlueprintName = "NightscoutRemoteCGMPlugin" + ReferencedContainer = "container:NightscoutRemoteCGM/NightscoutRemoteCGM.xcodeproj"> @@ -398,6 +398,20 @@ ReferencedContainer = "container:NightscoutService/NightscoutService.xcodeproj"> + + + + - - - - - - - - + + + + ") + exit(1) +} +let pullRequestName = CommandLine.arguments[1] // example: "LOOP-4688 DIY Sync" +let syncBranch = CommandLine.arguments[2] // example: "ps/LOOP-4688/diy-sync" + +enum EnvError: Error { + case missing(String) +} + +func getEnv(_ name: String) throws -> String { + guard let value = ProcessInfo.processInfo.environment[name] else { + throw EnvError.missing(name) + } + return value +} + +let ghUsername = try getEnv("GH_USERNAME") +let ghToken = try getEnv("GH_TOKEN") +let ghCommitterName = try getEnv("GH_COMMITTER_NAME") +let ghCommitterEmail = try getEnv("GH_COMMITTER_EMAIL") + +struct Project { + let project: String + let branch: String + let subdir: String + + init(_ project: String, _ branch: String, _ subdir: String = "") { + self.project = project + self.branch = branch + self.subdir = subdir + } + + var path: String { + if subdir.isEmpty { + return project + } else { + return subdir + "/" + project + } + } +} + +let projects = [ + Project("Loop", "dev"), + Project("LoopKit", "dev"), + Project("TidepoolService", "dev"), + Project("CGMBLEKit", "dev"), + Project("dexcom-share-client-swift", "dev"), + Project("RileyLinkKit", "dev"), + Project("NightscoutService", "dev"), + Project("LoopOnboarding", "dev"), + Project("AmplitudeService", "dev"), + Project("LogglyService", "dev"), + Project("MixpanelService", "main"), + Project("OmniBLE", "dev"), + Project("NightscoutRemoteCGM", "dev"), + Project("LoopSupport", "dev"), + Project("G7SensorKit", "main"), + Project("OmniKit", "main"), + Project("MinimedKit", "main"), + Project("LibreTransmitter", "main") +] + +let fm = FileManager.default +let loopkit = URL(string: "https://github.com/LoopKit")! +let tidepool = URL(string: "https://github.com/tidepool-org")! +let incomingRemote = "tidepool" + +let octokit = Octokit(TokenConfiguration(ghToken)) + +let credentials = Credentials.plaintext(username: ghUsername, password: ghToken) +let signature = try! Signature(name: ghCommitterName, email: ghCommitterEmail) + +for project in projects { + let dest = URL(string: fm.currentDirectoryPath)!.appendingPathComponent(project.path) + let repository: AsyncSwiftGit.Repository + if !fm.fileExists(atPath: dest.path) { + print("Cloning \(project.project)") + let url = loopkit.appendingPathComponent(project.project) + repository = try await Repository.clone(from: url, to: dest) + print("Cloned \(project.project)") + } else { + print("Already Exists: \(project.path)") + repository = try Repository(openAt: dest) + } + + let incomingRemoteURL = tidepool.appendingPathComponent(project.project) + + // Add remote if it doesn't exist, and fetch latest changes + if (try? repository.remoteURL(for: incomingRemote)) == nil { + try repository.addRemote(incomingRemote, url: incomingRemoteURL) + } + try await repository.fetch(remote: incomingRemote) + + // Create and checkout the branch where sync changesets will go ("tidepool-sync") + if !(try repository.branchExists(named: syncBranch)) { + try repository.createBranch(named: syncBranch, target: "origin/\(project.branch)") + } + try await repository.checkout(revspec: syncBranch) + + // Merge changes from tidepool to diy + try await repository.merge(revisionSpecification: "\(incomingRemote)/\(project.branch)", signature: signature) + + let originTree = try repository.lookupTree(for: "origin/\(project.branch)") + let diff = try repository.diff(originTree, repository.headTree) + + guard diff.count > 0 else { + print("No incoming changes; skipping PR creation.") + try await repository.checkout(revspec: project.branch) + continue + } + print("Found diffs: \(diff)") + + // Push changes up to origin + let refspec = "refs/heads/" + syncBranch + ":refs/heads/" + syncBranch + print("Pushing \(refspec) to \(project.project)") + try await repository.push(remoteName: "origin", refspecs: [refspec], credentials: credentials) + + if createPRs { + // Make sure a PR exists, or create it + + let prs = try await octokit.pullRequests(owner: "LoopKit", repository: project.project, base: project.branch, head:"LoopKit:" + syncBranch) + let pr: PullRequest + if prs.count == 0 { + pr = try await octokit.createPullRequest(owner: "LoopKit", repo: project.project, title: pullRequestName, head: "LoopKit:" + syncBranch, base: project.branch, body: "") + print("PR = \(pr)") + } else { + pr = prs.first! + } + if let url = pr.htmlURL { + if NSWorkspace.shared.open(url) { + print("default browser was successfully opened") + } + } + } else { + print("Skipping PR creation") + } +} + diff --git a/Scripts/update_submodule_refs.sh b/Scripts/update_submodule_refs.sh index fe24d7f01..c221eb3a6 100755 --- a/Scripts/update_submodule_refs.sh +++ b/Scripts/update_submodule_refs.sh @@ -1,6 +1,6 @@ #!/bin/zsh -projects=(LoopKit:AmplitudeService:dev LoopKit:CGMBLEKit:dev LoopKit:G7SensorKit:main LoopKit:LogglyService:dev LoopKit:Loop:dev LoopKit:LoopKit:dev LoopKit:LoopOnboarding:dev LoopKit:LoopSupport:dev LoopKit:NightscoutAPIClient:master ps2:NightscoutService:dev LoopKit:OmniBLE:dev LoopKit:TidepoolKit:dev LoopKit:TidepoolService:dev LoopKit:dexcom-share-client-swift:dev ps2:rileylink_ios:dev LoopKit:OmniKit:main LoopKit:MinimedKit:main) +projects=(LoopKit:AmplitudeService:dev LoopKit:CGMBLEKit:dev LoopKit:G7SensorKit:main LoopKit:LogglyService:dev LoopKit:Loop:dev LoopKit:LoopKit:dev LoopKit:LoopOnboarding:dev LoopKit:LoopSupport:dev LoopKit:NightscoutRemoteCGM:dev LoopKit:NightscoutService:dev LoopKit:OmniBLE:dev LoopKit:TidepoolService:dev LoopKit:dexcom-share-client-swift:dev ps2:RileyLinkKit:dev LoopKit:OmniKit:main LoopKit:MinimedKit:main LoopKit:LibreTransmitter:main LoopKit:MixpanelService:main) for project in ${projects}; do echo "Updating to $project" @@ -8,6 +8,7 @@ for project in ${projects}; do echo "Updating to $branch on $user/$project" cd $dir git checkout $branch + #git branch -D tidepool-sync git pull cd - done diff --git a/TidepoolKit b/TidepoolKit deleted file mode 160000 index b410a88ab..000000000 --- a/TidepoolKit +++ /dev/null @@ -1 +0,0 @@ -Subproject commit b410a88ab85bf3f1311170f08604fa1ede904c2e diff --git a/TidepoolService b/TidepoolService index ea68eef39..6e007cbde 160000 --- a/TidepoolService +++ b/TidepoolService @@ -1 +1 @@ -Subproject commit ea68eef39b3d5e51d315f7d1fd1428cad8fbe88c +Subproject commit 6e007cbde9920a9d8f25ae855deb36f989a2ce0f diff --git a/dexcom-share-client-swift b/dexcom-share-client-swift index 20ed7a145..21d8657d7 160000 --- a/dexcom-share-client-swift +++ b/dexcom-share-client-swift @@ -1 +1 @@ -Subproject commit 20ed7a1457af2e5a72fcc1c91b660ae8c0478d94 +Subproject commit 21d8657d727f26df76342188e580022bf4884714 diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 44f157337..f918b3aa4 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -60,7 +60,7 @@ platform :ios do "com.#{TEAMID}.loopkit.Loop.LoopWatch.watchkitextension", "com.#{TEAMID}.loopkit.Loop.LoopWatch", "com.#{TEAMID}.loopkit.Loop.Loop-Intent-Extension", - "com.#{TEAMID}.loopkit.Loop.SmallStatusWidget" + "com.#{TEAMID}.loopkit.Loop.LoopWidgetExtension" ] ) @@ -123,9 +123,9 @@ platform :ios do update_code_signing_settings( path: "#{GITHUB_WORKSPACE}/Loop/Loop.xcodeproj", - profile_name: mapping["com.#{TEAMID}.loopkit.Loop.SmallStatusWidget"], + profile_name: mapping["com.#{TEAMID}.loopkit.Loop.LoopWidgetExtension"], code_sign_identity: "iPhone Distribution", - targets: ["SmallStatusWidgetExtension"] + targets: ["Loop Widget Extension"] ) gym( @@ -181,7 +181,9 @@ platform :ios do Spaceship::ConnectAPI::BundleIdCapability::Type::APP_GROUPS, Spaceship::ConnectAPI::BundleIdCapability::Type::HEALTHKIT, Spaceship::ConnectAPI::BundleIdCapability::Type::PUSH_NOTIFICATIONS, - Spaceship::ConnectAPI::BundleIdCapability::Type::SIRIKIT + Spaceship::ConnectAPI::BundleIdCapability::Type::SIRIKIT, + Spaceship::ConnectAPI::BundleIdCapability::Type::USERNOTIFICATIONS_TIMESENSITIVE, + Spaceship::ConnectAPI::BundleIdCapability::Type::NFC_TAG_READING ]) configure_bundle_id("Loop Intent Extension", "com.#{TEAMID}.loopkit.Loop.Loop-Intent-Extension", [ @@ -199,7 +201,7 @@ platform :ios do Spaceship::ConnectAPI::BundleIdCapability::Type::SIRIKIT ]) - configure_bundle_id("Small Status Widget", "com.#{TEAMID}.loopkit.Loop.SmallStatusWidget", [ + configure_bundle_id("Loop Widget Extension", "com.#{TEAMID}.loopkit.Loop.LoopWidgetExtension", [ Spaceship::ConnectAPI::BundleIdCapability::Type::APP_GROUPS ]) @@ -226,7 +228,7 @@ platform :ios do "com.#{TEAMID}.loopkit.Loop.LoopWatch.watchkitextension", "com.#{TEAMID}.loopkit.Loop.LoopWatch", "com.#{TEAMID}.loopkit.Loop.Loop-Intent-Extension", - "com.#{TEAMID}.loopkit.Loop.SmallStatusWidget", + "com.#{TEAMID}.loopkit.Loop.LoopWidgetExtension", ] ) end @@ -247,6 +249,13 @@ platform :ios do end find_bundle_id("com.#{TEAMID}.loopkit.Loop") + + match( + type: "appstore", + git_basic_authorization: Base64.strict_encode64("#{GITHUB_REPOSITORY_OWNER}:#{GH_PAT}"), + app_identifier: [], + ) + end desc "Nuke Certs" diff --git a/fastlane/testflight.md b/fastlane/testflight.md index aa0c6ec1b..abe99314e 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 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: @@ -37,7 +54,7 @@ This step is common for all GitHub Browser Builds; do this step only once. You w 1. Sign in to the [Apple developer portal page](https://developer.apple.com/account/resources/certificates/list). 1. Copy the Team ID from the upper right of the screen. Record this as your `TEAMID`. -1. Go to the [App Store Connect](https://appstoreconnect.apple.com/access/api) interface, click the "Keys" tab, and create a new key with "Admin" access. Give it the name: "FastLane API Key". +1. Go to the [App Store Connect](https://appstoreconnect.apple.com/access/integrations/api) interface, click the "Integrations" tab, and create a new key with "Admin" access. Give it the name: "FastLane API Key". 1. Record the issuer id; this will be used for `FASTLANE_ISSUER_ID`. 1. Record the key id; this will be used for `FASTLANE_KEY_ID`. 1. Download the API key itself, and open it in a text editor. The contents of this file will be used for `FASTLANE_KEY`. Copy the full text, including the "-----BEGIN PRIVATE KEY-----" and "-----END PRIVATE KEY-----" lines. @@ -48,8 +65,8 @@ 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 selection to 90 days. - * Select the `repo` permission scope. + * Change the Expiration selection to `No expiration`. + * 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,19 +149,11 @@ 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 | -## Add Time Sensitive Notifications to Loop App ID -1. Go to [Certificates, Identifiers & Profiles](https://developer.apple.com/account/resources/identifiers/list) on the apple developer site. -1. Click on the "Loop" identifier -1. Scroll down to "Time Sensitive Notifications" -1. Tap the check box to enable Time Sensitive Notifications. -1. Click "Save". -1. Click "Confirm". - ## Create Loop App in App Store Connect If you have created a Loop app in App Store Connect before, you can skip this section. @@ -160,7 +169,7 @@ 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". @@ -181,3 +190,80 @@ You do not need to fill out the next form. That is for submitting to the app sto ## 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 of the month at 06: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 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 diff --git a/patches/save_patches_here.md b/patches/save_patches_here.md new file mode 100644 index 000000000..3320e1041 --- /dev/null +++ b/patches/save_patches_here.md @@ -0,0 +1 @@ +LoopWorkspace-level patches can be saved in this directory (LoopWorkspace/patches/) diff --git a/rileylink_ios b/rileylink_ios deleted file mode 160000 index 6b2d3c45c..000000000 --- a/rileylink_ios +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 6b2d3c45c6dfaeb6ea2a9da5111e7a2a5e2dc38e