Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 86 additions & 0 deletions .buildkite/cleanup-pr-build-branches.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
#!/bin/bash

set -euo pipefail

# Deletes `pr-build/<n>` branches whose PR is closed (merged or rejected).
# Runs on trunk pushes so the just-merged PR's branch gets cleaned up
# immediately, and any orphans accumulated from prior failures get swept too.

if [[ "${BUILDKITE_BRANCH:-}" != "trunk" ]]; then
echo "Not a trunk build (branch=${BUILDKITE_BRANCH:-unset}), skipping"
exit 0
fi

if [[ -z "${GITHUB_TOKEN:-}" ]]; then
echo "GITHUB_TOKEN not set, cannot query PR state" >&2
exit 1
fi

GITHUB_REPO="wordpress-mobile/GutenbergKit"

echo '--- :robot_face: Use bot for Git operations'
source use-bot-for-git

echo "--- :mag: Listing pr-build/* branches on origin"
mapfile -t branches < <(
git ls-remote --heads origin 'refs/heads/pr-build/*' \
| awk '{print $2}' \
| sed 's|^refs/heads/||'
)

echo "Found ${#branches[@]} pr-build branches"

if [[ ${#branches[@]} -eq 0 ]]; then
exit 0
fi

echo "--- :github: Checking PR state for each branch"
to_delete=()
for branch in "${branches[@]}"; do
pr_number="${branch#pr-build/}"
if ! [[ "$pr_number" =~ ^[0-9]+$ ]]; then
echo "Skipping $branch (unexpected suffix)"
continue
fi

response=$(
curl --silent --show-error \
--write-out $'\n%{http_code}' \
--header "Authorization: Bearer ${GITHUB_TOKEN}" \
--header "Accept: application/vnd.github+json" \
--header "X-GitHub-Api-Version: 2022-11-28" \
"https://api.github.com/repos/${GITHUB_REPO}/pulls/${pr_number}"
)
http_code=$(printf '%s' "$response" | tail -n1)
body=$(printf '%s' "$response" | sed '$d')

if [[ "$http_code" != "200" ]]; then
echo "Skipping $branch (HTTP $http_code from GitHub)"
continue
fi

state=$(printf '%s' "$body" | jq -r '.state')

Comment on lines +57 to +63
if [[ "$state" == "closed" ]]; then
echo "Marking $branch for deletion (PR #$pr_number is closed)"
to_delete+=("$branch")
else
echo "Keeping $branch (PR #$pr_number is $state)"
fi
done

if [[ ${#to_delete[@]} -eq 0 ]]; then
echo "No closed PR branches to delete"
exit 0
fi

echo "--- :wastebasket: Deleting ${#to_delete[@]} stale branches"
chunk_size=50
for ((i=0; i<${#to_delete[@]}; i+=chunk_size)); do
chunk=("${to_delete[@]:i:chunk_size}")
refspecs=()
for branch in "${chunk[@]}"; do
refspecs+=(":refs/heads/${branch}")
done
git push origin "${refspecs[@]}"
done
16 changes: 14 additions & 2 deletions .buildkite/pipeline.yml
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,8 @@ steps:
- label: ':xcode: Build XCFramework'
key: build-xcframework
depends_on:
- build-react
- swift-test-library
- build-react
- swift-test-library
command: |
buildkite-agent artifact download dist.tar.gz .
tar -xzf dist.tar.gz
Expand All @@ -97,6 +97,7 @@ steps:

- label: ':s3: Publish XCFramework to S3'
depends_on: build-xcframework
if: build.pull_request.id == null
command: |
buildkite-agent artifact download '*.xcframework.zip' .
buildkite-agent artifact download '*.xcframework.zip.checksum.txt' .
Expand All @@ -107,6 +108,12 @@ steps:
bundle exec fastlane publish_to_s3 version:${NEW_VERSION:-${BUILDKITE_TAG:-$BUILDKITE_COMMIT}}
plugins: *plugins

- label: ':swift: :package: Publish PR XCFramework'
depends_on: build-xcframework
if: build.pull_request.id != null
command: .buildkite/publish-pr-xcframework.sh
plugins: *plugins

- label: ':ios: Test iOS E2E'
depends_on: build-react
command: |
Expand Down Expand Up @@ -149,3 +156,8 @@ steps:
- 'android/Gutenberg/build/outputs/androidTest-results/connected/**/*'
- 'android/Gutenberg/build/outputs/buildkite-logs/**/*'
- 'android/Gutenberg/build/outputs/connected_android_test_additional_output/**/*'

- label: ':wastebasket: Clean up `pr-build/*` branches for closed PRs'
if: build.branch == "trunk"
command: .buildkite/cleanup-pr-build-branches.sh
plugins: *plugins
30 changes: 30 additions & 0 deletions .buildkite/publish-pr-xcframework.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#!/bin/bash
set -euo pipefail

if [[ "${BUILDKITE_PULL_REQUEST:-false}" == "false" ]]; then
echo "Not a PR build, skipping PR XCFramework publish"
exit 0
fi

# Skip on fork PRs: bot credentials and S3 secrets aren't available, and we
# couldn't push the snapshot branch back to the canonical repo anyway.
if [[ -n "${BUILDKITE_PULL_REQUEST_REPO:-}" ]] \
&& [[ "$BUILDKITE_PULL_REQUEST_REPO" != *"wordpress-mobile/GutenbergKit"* ]]; then
echo "PR is from a fork (${BUILDKITE_PULL_REQUEST_REPO}), skipping XCFramework publish"
exit 0
fi

PR_NUMBER="$BUILDKITE_PULL_REQUEST"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nitpick. Might as well have read BUILDKITE_PULL_REQUEST directly.


echo '--- :robot_face: Use bot for Git operations'
source use-bot-for-git

echo '--- :arrow_down: Downloading XCFramework artifacts'
buildkite-agent artifact download '*.xcframework.zip' . --step "build-xcframework"
buildkite-agent artifact download '*.xcframework.zip.checksum.txt' . --step "build-xcframework"

echo '--- :rubygems: Setting up Gems'
install_gems

echo "--- :rocket: Publishing PR build for PR #${PR_NUMBER}"
bundle exec fastlane publish_pr_xcframework pr_number:"$PR_NUMBER"
110 changes: 110 additions & 0 deletions fastlane/Fastfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ PROJECT_ROOT = File.expand_path('..', __dir__)

APPLE_TEAM_ID = 'PZYM8XX95Q'

GITHUB_REPO = 'wordpress-mobile/GutenbergKit'
XCFRAMEWORK_COMMENT_MARKER = '<!-- gutenbergkit-xcframework-build -->'

ASC_API_KEY_ENV_VARS = %w[
APP_STORE_CONNECT_API_KEY_KEY_ID
APP_STORE_CONNECT_API_KEY_ISSUER_ID
Expand Down Expand Up @@ -39,6 +42,21 @@ lane :publish_to_s3 do |options|
)
end

lane :publish_pr_xcframework do |options|
pr_number = options[:pr_number].to_s
UI.user_error!('pr_number is required') if pr_number.empty?

branch_name = "pr-build/#{pr_number}"
version = "pr-builds/#{pr_number}"

publish_to_s3(version: version)
push_xcframework_snapshot_branch(branch_name: branch_name, version: version, checksum: xcframework_checksum)
Comment on lines +45 to +53

body = xcframework_comment_body(branch_name: branch_name, commit_sha: ENV.fetch('BUILDKITE_COMMIT', nil))
upsert_pr_xcframework_comment(pr_number: pr_number, body: body)
post_buildkite_annotation(body: body)
end

lane :xcframework_sign do
sh(
'codesign',
Expand Down Expand Up @@ -123,3 +141,95 @@ def get_required_env!(key)

UI.user_error!("Environment variable `#{key}` is not set.")
end

def push_xcframework_snapshot_branch(branch_name:, version:, checksum:)
package_swift = File.join(PROJECT_ROOT, 'Package.swift')
rewrite_resources_mode!(package_swift, version: version, checksum: checksum)

sh("git checkout -B #{branch_name}")
git_commit(path: package_swift, message: "Update Package.swift for #{version}")
sh("git push -f origin #{branch_name}")
end

def rewrite_resources_mode!(package_swift, version:, checksum:)
prefix = 'let resourcesMode: DependencyMode ='
replacement = %(#{prefix} .release(version: "#{version}", checksum: "#{checksum}")\n)

lines = File.readlines(package_swift)
matches = lines.count { |line| line.start_with?(prefix) }
UI.user_error!("Expected exactly one `#{prefix}` line in Package.swift, found #{matches}") unless matches == 1

rewritten = lines.map { |line| line.start_with?(prefix) ? replacement : line }
File.write(package_swift, rewritten.join)
end

def xcframework_comment_body(branch_name:, commit_sha:)
short_sha = (commit_sha || 'unknown')[0, 8]
<<~MARKDOWN
#{XCFRAMEWORK_COMMENT_MARKER}
## XCFramework Build

This PR's XCFramework is available for testing. Add to your `Package.swift`:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nitpick.

Suggested change
This PR's XCFramework is available for testing. Add to your `Package.swift`:
This PR's XCFramework is available for testing. Add this to your `Package.swift`:

or

Suggested change
This PR's XCFramework is available for testing. Add to your `Package.swift`:
This PR's XCFramework is available for testing. Add the following to your `Package.swift`:


```swift
.package(url: "https://github.com/#{GITHUB_REPO}", branch: "#{branch_name}")
```

<sub>Built from #{short_sha}</sub>
MARKDOWN
Comment on lines +166 to +179
end

def upsert_pr_xcframework_comment(pr_number:, body:)
github_token = ENV.fetch('GITHUB_TOKEN', nil)
unless github_token
UI.important('GITHUB_TOKEN not set, skipping PR comment')
return
end

existing = find_existing_xcframework_comment(github_token: github_token, pr_number: pr_number)
if existing
github_api(
server_url: 'https://api.github.com',
api_token: github_token,
http_method: 'PATCH',
path: "/repos/#{GITHUB_REPO}/issues/comments/#{existing['id']}",
body: { body: body }
)
else
github_api(
server_url: 'https://api.github.com',
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nitpick. This URL is duplicated here and in the lane below. I could be extracted in a constant.

api_token: github_token,
http_method: 'POST',
path: "/repos/#{GITHUB_REPO}/issues/#{pr_number}/comments",
body: { body: body }
)
end
end

def find_existing_xcframework_comment(github_token:, pr_number:)
page = 1
loop do
result = github_api(
server_url: 'https://api.github.com',
api_token: github_token,
http_method: 'GET',
path: "/repos/#{GITHUB_REPO}/issues/#{pr_number}/comments?per_page=100&page=#{page}"
)
comments = result[:json] || []
return nil if comments.empty?

found = comments.find { |c| c['body']&.include?(XCFRAMEWORK_COMMENT_MARKER) }
return found if found
return nil if comments.length < 100

page += 1
end
end

def post_buildkite_annotation(body:)
return unless ENV['BUILDKITE_AGENT_ACCESS_TOKEN']

IO.popen(['buildkite-agent', 'annotate', '--context', 'xcframework', '--style', 'info'], 'w') do |io|
io.write(body)
end
Comment on lines +232 to +234
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

We have buildkite_annotate available in release-toolkit. Example usage in WordPress iOS.

buildkite_annotate(context: 'code-freeze-success', style: 'success', message: message) if is_ci

So, maybe here this would work

Suggested change
IO.popen(['buildkite-agent', 'annotate', '--context', 'xcframework', '--style', 'info'], 'w') do |io|
io.write(body)
end
buildkite_annotate(context: 'xcframework', style: 'info', message: body)

end
Comment on lines +229 to +235
Loading