Skip to content

chore: Parallelize CI builds to reduce wall clock time#95

Merged
kinyoklion merged 12 commits intomainfrom
devin/1772837688-parallelize-ci-builds
Mar 9, 2026
Merged

chore: Parallelize CI builds to reduce wall clock time#95
kinyoklion merged 12 commits intomainfrom
devin/1772837688-parallelize-ci-builds

Conversation

@devin-ai-integration
Copy link
Contributor

@devin-ai-integration devin-ai-integration bot commented Mar 6, 2026

Requirements

  • I have added test coverage for new or changed functionality
  • I have followed the repository's pull request submission guidelines
  • I have validated my changes against all supported platform versions

Related issues

Ports the relevant CI changes from launchdarkly/ios-client-sdk#481 to swift-eventsource to reduce CI build times. Also incorporates the contract test fix from branch devin/1772832423-fix-contract-test-version.

The contract tests previously failed due to GitHub API rate limiting in the sse-contract-tests downloader script. The root cause fix is in launchdarkly/sse-contract-tests#34.

Describe the solution you've provided

The monolithic .github/actions/ci composite action ran all builds, tests, linting, and contract tests sequentially in a single job. This PR breaks it into focused composite actions that run as separate parallel jobs in ci.yml:

New action What it does
lint brew install swiftlint, pod lib lint --quick, swiftlint lint
build-ios Build for iOS device + test on iOS Simulator
build-macos Build & test on macOS + ARM64 build
build-tvos Build for tvOS device + test on tvOS Simulator
build-watchos Build for watchOS device + simulator
test-swiftpm swift test via SwiftPM
contract-tests Build service, start in background, run via launchdarkly/gh-actions/actions/contract-tests@main
build-docs (already existed, now its own job with setup-xcode)

Other changes:

  • Mint removedMintfile deleted; swiftlint installed directly via brew install swiftlint
  • Simulator warmup added for iOS/tvOS/watchOS (xcrun simctl list devices available) to mitigate intermittent macos-15 runner issues (actions/runner-images#12948)
  • Pod lint changed from pod spec lint to pod lib lint --allow-warnings --quick (skips full build since dedicated jobs already compile all platforms)
  • setup-xcode moved to workflow level (each job sets up Xcode explicitly)
  • Contract tests fixed — uses the shared launchdarkly/gh-actions/actions/contract-tests@main action (intentionally @main — this is our own organization's action). Configured with branch: main (since sse-contract-tests uses main, not v2, as its default branch) and enable_persistence_tests: false (SSE contract tests don't support that flag). Two known-flaky tests are skipped via extra_params.
  • Release/publish workflows (release-please.yml, manual-publish.yml, manual-publish-docs.yml) updated to call individual composite actions sequentially (these must remain sequential since they gate the publish step)

Checklist for human reviewer

  1. SwiftLint version is no longer pinned — Mintfile pinned realm/SwiftLint@0.56.2; now brew install swiftlint pulls whatever Homebrew provides. Verify this won't cause unexpected lint failures.
  2. Pod lint command changed — switched from pod spec lint to pod lib lint with --allow-warnings --quick. Confirm lib lint is appropriate here (validates local podspec vs. published spec).
  3. Runner usage — each parallel job spins up its own macOS runner (8 macOS jobs instead of 1). Wall clock time decreases but total runner-minutes may increase.
  4. Contract test action uses @main — uses launchdarkly/gh-actions/actions/contract-tests@main (mutable ref) rather than a pinned SHA. This is intentional per repo owner direction — the action is from LaunchDarkly's own organization.

Describe alternatives you've considered

  • Could have kept Mint with version pinning — chose to match ios-client-sdk pattern which removed Mint.
  • Could have created a matrix for multiple Xcode versions — kept single version (16.4) to match current setup.

Additional context


Note

Medium Risk
CI-only changes, but they restructure job orchestration, switch lint/tool installation sources, and move contract testing to an external action ref (@main), any of which can cause unexpected pipeline failures or behavior changes.

Overview
Refactors GitHub Actions CI from a single composite (.github/actions/ci) into multiple focused composite actions (lint, per-platform Xcode builds/tests, SwiftPM tests, and contract tests) and updates ci.yml to run them as separate parallel jobs on macos-15 (with Xcode 16.4 setup per job) to reduce wall-clock time.

CI reliability and tooling are adjusted: adds simulator “warm-up” (xcrun simctl list devices available) for iOS/tvOS/watchOS, switches pod linting to pod lib lint --allow-warnings --quick, removes Mint/Mintfile in favor of brew install swiftlint, and updates contract tests to run via launchdarkly/gh-actions/actions/contract-tests@main with specific config and skipped cases.

Release/publish workflows (manual-publish*, release-please) are updated to invoke the new actions sequentially (preserving gating before publish).

Written by Cursor Bugbot for commit b2e2d4b. This will update automatically on new commits. Configure here.

- Break monolithic ci composite action into focused actions:
  lint, build-ios, build-macos, build-tvos, build-watchos,
  test-swiftpm, contract-tests
- Run each as a separate parallel job in ci.yml
- Remove Mint in favor of direct brew install swiftlint
- Add simulator warmup step for iOS/tvOS/watchOS builds
- Use --quick flag for pod lib lint since dedicated build jobs compile
- Update release-please, manual-publish, and manual-publish-docs
  workflows to use new individual composite actions
- Add setup-xcode step to build-docs job

Co-Authored-By: rlamb@launchdarkly.com <rlamb@launchdarkly.com>
@devin-ai-integration
Copy link
Contributor Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

devin-ai-integration bot and others added 2 commits March 6, 2026 23:04
…download

Incorporates fix from devin/1772832423-fix-contract-test-version branch.
Replaces make contract-tests with launchdarkly/gh-actions/actions/contract-tests@contract-tests-v1.3.0
which properly downloads the test harness.

Co-Authored-By: rlamb@launchdarkly.com <rlamb@launchdarkly.com>
The sse-contract-tests binary does not support the -enable-persistence-tests
flag that contract-tests-v1.3.0 enables by default.

Co-Authored-By: rlamb@launchdarkly.com <rlamb@launchdarkly.com>
run: make start-contract-test-service-bg

- name: Run contract tests
uses: launchdarkly/gh-actions/actions/contract-tests@contract-tests-v1.3.0
Copy link

@semgrep-code-launchdarkly semgrep-code-launchdarkly bot Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An action sourced from a third-party repository on GitHub is not pinned to a full length commit SHA. Pinning an action to a full length commit SHA is currently the only way to use an action as an immutable release. Pinning to a particular SHA helps mitigate the risk of a bad actor adding a backdoor to the action's repository, as they would need to generate a SHA-1 collision for a valid Git object payload.

Fixed in commit 8e990e4

devin-ai-integration bot and others added 4 commits March 6, 2026 23:08
…ract test version resolution

Co-Authored-By: rlamb@launchdarkly.com <rlamb@launchdarkly.com>
Pins launchdarkly/gh-actions/actions/contract-tests to full commit SHA
5adb11fd6953e1bc35d9cf1fc1b4374c464e3a8b (contract-tests-v1.3.0) to
mitigate supply chain risk.

Co-Authored-By: rlamb@launchdarkly.com <rlamb@launchdarkly.com>
…release resolution

Co-Authored-By: rlamb@launchdarkly.com <rlamb@launchdarkly.com>
The upstream sse-contract-tests/downloader/run.sh does not pass the
GITHUB_TOKEN to the GitHub API call that resolves partial version
strings (e.g. 'v2' -> 'v2.31.0'). This causes rate-limit failures
on shared CI runners. Using an inline script that passes the token
fixes the issue.

Co-Authored-By: rlamb@launchdarkly.com <rlamb@launchdarkly.com>
kinyoklion added a commit to launchdarkly/sse-contract-tests that referenced this pull request Mar 9, 2026
…cript (#34)

**Requirements**

- [x] I have added test coverage for new or changed functionality
- [x] I have followed the repository's [pull request submission
guidelines](../blob/master/CONTRIBUTING.md#submitting-pull-requests)
- [x] I have validated my changes against all supported platform
versions

**Related issues**

The downloader script's `resolve_version()` function calls the GitHub
releases API without authentication, causing `403` rate-limit failures
on shared CI runners. This was discovered while debugging contract test
failures in
[launchdarkly/swift-eventsource#95](launchdarkly/swift-eventsource#95).
The equivalent script in
[sdk-test-harness](https://github.com/launchdarkly/sdk-test-harness/blob/v2/downloader/run.sh)
already has token support.

**Describe the solution you've provided**

Rewrites `downloader/run.sh` to fix the auth bug and add observability
and cross-platform support:

1. **GitHub token auth** — passes `GITHUB_TOKEN` (via `Authorization`
header) to all API and download requests, avoiding rate limiting.
2. **Cross-platform support** — adds Windows detection
(MSYS/MINGW/CYGWIN) with `zip` extraction, `.exe` extension handling,
and normalizes `aarch64` → `arm64`.
3. **HTTP request logging** — new `do_curl()` helper logs the request
URL, HTTP status line, content-type, and `x-ratelimit-remaining` for
every request.
4. **Version resolution logging** — lists all available versions and the
resolved version during partial version resolution.
5. **Platform info** — outputs OS type, architecture, and archive name
at startup.
6. **Bug fix** — changes `exit` → `return` in `resolve_version()` so it
doesn't kill the entire script when called from a `$()` subshell.
7. **Download-only mode** — `PARAMS` is now optional. When omitted, the
script downloads and extracts the binary without executing it.

Adds a new `test-downloader.yml` CI workflow that verifies the script on
Linux x64, Linux arm64, macOS, and Windows — testing partial (`v2`) and
full (`v2.31.0`) version resolution in download-only mode, plus a step
that invokes the actual
[`launchdarkly/gh-actions/actions/contract-tests`](https://github.com/launchdarkly/gh-actions/tree/main/actions/contract-tests)
action (with `continue-on-error` since no test service is running).

**Suggested review checklist:**

- [ ] The `do_curl()` function uses `eval` to construct curl commands
(needed for proper header quoting). This matches the `sdk-test-harness`
pattern — verify this is acceptable.
- [ ] On Windows, the `[ ! -x "${EXECUTABLE}" ]` check may always be
true since MSYS doesn't set Unix execute bits on extracted `.exe` files
— this would cause re-downloads but is not harmful. Worth considering.
- [ ] The `continue-on-error: true` on the `gh-actions/contract-tests`
CI step means a download failure there won't fail CI. The direct
download steps (partial + full version) are the strict verification; the
action step just confirms compatibility.
- [ ] Making `PARAMS` optional is a behavioral change. Existing callers
(like `gh-actions/contract-tests`) always pass `PARAMS`, so this should
be safe, but confirm no consumers rely on the old error behavior.
- [ ] All informational output goes to stderr (via `log()`). This is
correct for command substitution but means CI logs interleave
stderr/stdout.

**Describe alternatives you've considered**


Could have forked the `gh-actions/contract-tests` action to call a fixed
downloader script, but that would leave the bug in this repo for anyone
using the script directly.

**Additional context**

- Devin session:
https://app.devin.ai/sessions/2da8679d630649d4be8984e8cab6c980
- Requested by: rlamb@launchdarkly.com
<!-- devin-review-badge-begin -->

---

<a
href="https://app.devin.ai/review/launchdarkly/sse-contract-tests/pull/34"
target="_blank">
  <picture>
<source media="(prefers-color-scheme: dark)"
srcset="https://static.devin.ai/assets/gh-open-in-devin-review-dark.svg?v=1">
<img
src="https://static.devin.ai/assets/gh-open-in-devin-review-light.svg?v=1"
alt="Open with Devin">
  </picture>
</a>
<!-- devin-review-badge-end -->

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Touches the release download/execution path and introduces OS-specific
branching plus `eval`-based curl invocation, which could break downloads
on some runners if edge cases were missed.
> 
> **Overview**
> Fixes the `downloader/run.sh` release downloader to be more reliable
on CI by **supporting optional `GITHUB_TOKEN` auth** for GitHub API and
asset downloads and adding verbose HTTP logging (status, content-type,
rate-limit remaining).
> 
> Expands the script to **support Windows shells (MSYS/MINGW/CYGWIN)**
via OS/arch detection, `zip` extraction and `.exe` handling, normalizes
`aarch64`→`arm64`, and changes behavior so `PARAMS` is optional
(download-only mode) while also fixing `resolve_version()` to `return`
instead of exiting the whole script.
> 
> Adds a new GitHub Actions workflow `test-downloader.yml` that runs the
downloader on Linux x64/arm64, macOS, and Windows, testing both partial
(`v2`) and full (`v2.31.0`) version resolution, plus a best-effort
compatibility check via
`launchdarkly/gh-actions/actions/contract-tests@main`.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
2a29832. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: rlamb@launchdarkly.com <rlamb@launchdarkly.com>
@kinyoklion kinyoklion marked this pull request as ready for review March 9, 2026 16:05
@kinyoklion kinyoklion requested a review from a team as a code owner March 9, 2026 16:05
devin-ai-integration bot and others added 2 commits March 9, 2026 16:11
Co-Authored-By: rlamb@launchdarkly.com <rlamb@launchdarkly.com>
The shared gh-actions/contract-tests action defaults branch to 'v2',
but sse-contract-tests uses 'main' as its default branch. Without
this, the curl fetch of the downloader script returns a 404.

Co-Authored-By: rlamb@launchdarkly.com <rlamb@launchdarkly.com>
The sse-contract-tests binary does not support the -enable-persistence-tests
flag. The shared action defaults this to true, so we must explicitly disable it.

Co-Authored-By: rlamb@launchdarkly.com <rlamb@launchdarkly.com>
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

@kinyoklion kinyoklion self-requested a review March 9, 2026 16:23
@kinyoklion kinyoklion requested a review from keelerm84 March 9, 2026 16:26
version: v2
token: ${{ inputs.token }}
test_service_port: '8000'
test_harness_port: '8111'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a default value for the action.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed test_harness_port, stop_service, and enable_persistence_tests since they all match the action's defaults. Pushed in 8155ae5.

test_harness_port: '8111'
debug_logging: 'true'
stop_service: true
enable_persistence_tests: false
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is also a default.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, the shared action's action.yml defaults enable_persistence_tests to true (line 27), even though the README says false. Since sse-contract-tests doesn't support the -enable-persistence-tests flag, we need to explicitly set it to false here. I've re-added it.

I did remove test_harness_port and stop_service since those truly are defaults (8111 and true respectively).

devin-ai-integration bot and others added 2 commits March 9, 2026 16:40
Per reviewer feedback, remove test_harness_port, stop_service, and
enable_persistence_tests since they match the shared action's defaults.

Co-Authored-By: rlamb@launchdarkly.com <rlamb@launchdarkly.com>
The shared action defaults enable_persistence_tests to true (line 27 of
gh-actions/actions/contract-tests/action.yml), not false as the README
states. sse-contract-tests doesn't support that flag, so we must
explicitly disable it.

Co-Authored-By: rlamb@launchdarkly.com <rlamb@launchdarkly.com>
@kinyoklion kinyoklion merged commit 5674eb5 into main Mar 9, 2026
17 checks passed
@kinyoklion kinyoklion deleted the devin/1772837688-parallelize-ci-builds branch March 9, 2026 20:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants