Generated by Tom π’ β 2026-03-15 For Justin's fork: https://github.com/justinr1234/LoopWorkspace
Loop is an open-source iOS automated insulin delivery (AID) system. It runs on iPhone, reads CGM data (Dexcom G7, Libre, etc.), communicates with insulin pumps (Omnipod, Minimed), and uses an algorithm to automate insulin dosing. LoopWorkspace is the umbrella repository that ties together ~20 submodule repos into a single Xcode workspace.
Current version: 3.13.0 (build 57)
Default branch: dev
Upstream: LoopKit/LoopWorkspace
LoopWorkspace/
βββ .circleci/ # CircleCI config (build + test on simulator)
βββ .github/workflows/ # GitHub Actions (build β TestFlight pipeline)
βββ fastlane/ # Fastlane config (signing, build, upload)
βββ patches/ # β Custom patches applied during CI build
βββ Scripts/ # Translation/localization tooling
βββ docs/ # Single screenshot
βββ LoopWorkspace.xcworkspace # Xcode workspace (references all submodules)
βββ LoopConfigOverride.xcconfig # Build config overrides (team ID, features, bundle ID)
βββ VersionOverride.xcconfig # Version number (3.13.0 / build 57)
βββ InfoCustomizations.txt # Service config (TidepoolServiceClientId)
βββ OverrideAssetsLoop.xcassets # Custom app icon overrides
βββ OverrideAssetsWatchApp.xcassets # Custom watch app icon overrides
βββ Gemfile / Gemfile.lock # Ruby deps for fastlane
βββ README.md
All submodules point to LoopKit/ GitHub org repos. They fall into these categories:
| Submodule | Branch | Description |
|---|---|---|
| Loop | dev | Main iOS app β UI, dosing algorithm, data management |
| LoopKit | dev | Core framework β types, protocols, data models, stores |
| LoopOnboarding | dev | First-run onboarding flow |
| LoopSupport | dev | Support/diagnostic utilities |
| Submodule | Branch | Description |
|---|---|---|
| CGMBLEKit | dev | Dexcom G5/G6 BLE communication |
| G7SensorKit | main | Dexcom G7 support |
| dexcom-share-client-swift | dev | Dexcom Share cloud API client |
| NightscoutRemoteCGM | dev | Nightscout as CGM data source |
| LibreTransmitter | main | FreeStyle Libre sensor support |
| Submodule | Branch | Description |
|---|---|---|
| OmniBLE | dev | Omnipod DASH (BLE) communication |
| OmniKit | main | Omnipod Eros communication |
| MinimedKit | main | Medtronic Minimed pump support |
| RileyLinkKit | dev | RileyLink BLE bridge (for Eros/Minimed) |
| Submodule | Branch | Description |
|---|---|---|
| NightscoutService | dev | Nightscout data upload |
| TidepoolService | dev | Tidepool data platform integration |
| AmplitudeService | dev | Analytics (Amplitude) |
| LogglyService | dev | Logging (Loggly) |
| MixpanelService | dev | Analytics (Mixpanel) |
| Submodule | Branch | Description |
|---|---|---|
| TrueTime.swift | dev | NTP time synchronization |
| Minizip | dev | Zip/unzip utility |
- Clone with
--recurse-submodules - Open
LoopWorkspace.xcworkspacein Xcode - Set
LOOP_DEVELOPMENT_TEAMinLoopConfigOverride.xcconfig - Select the LoopWorkspace scheme (not the "Loop" scheme)
- Build/Run
Key config files:
LoopConfigOverride.xcconfigβ Team ID, bundle ID, feature flagsVersionOverride.xcconfigβ Marketing version (3.13.0) and build number (57)- Feature flags:
EXPERIMENTAL_FEATURES_ENABLED,SIMULATORS_ENABLED,ALLOW_ALGORITHM_EXPERIMENTS,DEBUG_FEATURES_ENABLED
This is the primary distribution method. No Mac needed.
Prerequisites:
- Apple Developer account ($99/year)
- 6 secrets configured in GitHub repo settings:
TEAMIDβ Apple Developer Team IDFASTLANE_KEY_IDβ App Store Connect API key IDFASTLANE_ISSUER_IDβ App Store Connect API issuerFASTLANE_KEYβ API private key contentGH_PATβ GitHub Personal Access Token (withrepo+workflowscopes)MATCH_PASSWORDβ Password for Match-Secrets repo (cert storage)
- Manual trigger only
- Validates GH_PAT format and permissions
- Tests App Store Connect API key
- Checks Match certificate storage
- Manual trigger only
- Creates Apple bundle identifiers via Fastlane
- Configures capabilities (App Groups, HealthKit, Push, Siri, NFC)
- Called by build workflow or manual trigger
- Uses
fastlane matchfor certificate management - Stores certs in private
Match-Secretsrepo - Auto-renews expired distribution certificates
- Variable
ENABLE_NUKE_CERTScontrols auto-cleanup
- Triggers:
- Manual (
workflow_dispatch) - Weekly cron: Sunday 7:33 UTC
- Manual (
- Flow:
- Check status β Sync fork with upstream, check for new commits
- Build decision β Build if: manual trigger, new commits found, OR 2nd Sunday of month
- Check certs β Validate/renew distribution certificates
- Build:
- Runs on
macos-15with Xcode 16.4 - Checkout with submodules
- β Apply patches from
patches/directory - Install Ruby deps (
bundle install) fastlane build_loopβ sign and archive IPA
- Runs on
- Deploy:
fastlane releaseβ Upload to TestFlight- Upload artifacts (IPA, dSYM, build log)
- Builds and tests on iOS Simulator (iPhone 16, iOS 18.5)
- Uses Xcode 16.4
- Runs
xcodebuild buildthenxcodebuild test
| Lane | Purpose |
|---|---|
build_loop |
Sign, build, archive Loop IPA |
release |
Upload IPA to TestFlight |
identifiers |
Create/configure Apple bundle IDs |
certs |
Provision certificates via Match |
validate_secrets |
Validate all secrets |
nuke_certs |
Remove all certificates |
check_and_renew_certificates |
Check cert expiry, flag if renewal needed |
The build_loop.yml workflow handles upstream syncing automatically:
- Uses
aormsby/Fork-Sync-With-Upstream-action@v3.4.1 - Syncs
TARGET_BRANCH(current branch) fromLoopKit/LoopWorkspacesame branch - Only runs when
github.repository_owner != 'LoopKit'(i.e., on forks) - Controlled by
SCHEDULED_SYNCvariable (default: enabled) - After sync, checks if new commits were found β triggers build if so
Important: Upstream sync uses shallow clone (6 months ago). The fork's branch is fast-forwarded to match upstream.
The build pipeline has a built-in patch system in the "Customize Loop" step:
# 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 patches (via curl from GitHub commits/PRs):
# curl https://github.com/.../commit.patch | git apply --directory=Loop -v --whitespace=fix- Workspace-level patches: Drop
.patchfiles inpatches/directory β applied withgit apply - Submodule patches: Use
curlto fetch patches from GitHub commits/PRs, apply with--directory=SubmoduleName - Patches are applied after checkout but before build
- This means patches survive upstream syncs β they're reapplied every build
The patches/ directory exists but is empty (just has save_patches_here.md).
The infrastructure already exists! We just need to:
- Create patches β Generate
.patchfiles for our customizations - Drop them in
patches/β Workspace-level changes - Add
curl | git apply --directory=Xlines β For submodule changes - Commit to our fork β Patches persist across upstream syncs
βββββββββββββββ ββββββββββββββββββββ βββββββββββββββββββ
β Upstream β β Justin's Fork β β TestFlight β
β LoopKit/ ββββββΆβ justinr1234/ ββββββΆβ (iPhone) β
β LoopWorkspace β LoopWorkspace β β β
βββββββββββββββ ββββββββββββββββββββ βββββββββββββββββββ
β
patches/ directory
+ custom curl lines
in build_loop.yml
- Justin needs to configure the 6 secrets in his fork's GitHub settings
- Create App Store Connect API key if not already done
- Set up Match-Secrets repository
- Identify a desired customization (e.g., custom Nightscout URL, algorithm tweaks)
- Generate a
.patchfile - Test locally with
git apply - Commit to
patches/directory - Trigger a build
- Tom can create/modify patches and commit them to the fork
- Each push triggers a build β TestFlight
- Justin installs updated Loop from TestFlight
# Generate a patch for workspace-level changes
cd ~/p/LoopWorkspace
# Make changes...
git diff > patches/my-customization.patch
# Generate a patch for submodule changes
cd ~/p/LoopWorkspace/Loop
# Make changes...
git diff > ../patches/loop-custom.patch
# Note: apply with --directory=Loop in build_loop.yml
# Or reference a GitHub commit directly in build_loop.yml:
# curl https://github.com/user/Loop/commit/abc123.patch | git apply --directory=Loop -v --whitespace=fix- Patch conflicts: When upstream updates, patches might fail to apply. The build will fail, alerting us to update patches.
- Submodule patches are trickier: Since submodules auto-update, patches against them may break more often. Consider:
- Pinning submodule versions (fork the submodule too)
- Using broader context in patches (
-C3or more) - Keeping patches minimal and targeted
- Testing: CircleCI runs tests on every push β use this to validate patches before they hit TestFlight.
- 90-day rule: TestFlight builds expire after 90 days. The auto-build system handles this, but custom patches must not break the build.
- LoopDocs: https://loopkit.github.io/loopdocs/
- Browser Build Guide: https://loopkit.github.io/loopdocs/browser/bb-overview/
- Mac Build Guide: https://loopkit.github.io/loopdocs/build/overview/
- Upstream Repo: https://github.com/LoopKit/LoopWorkspace
- Justin's Fork: https://github.com/justinr1234/LoopWorkspace
- Loop & Learn Customizations: https://www.loopandlearn.org/custom-code/