Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
fe86f77
Make CI upload xcactivitylog
mokagio Oct 16, 2025
01437fd
Focus - Delete CI steps not used
mokagio Oct 16, 2025
6cc0625
Use explicit DerivedData path in unit tests
mokagio Oct 16, 2025
ab82b12
Make DD path a constant
mokagio Oct 16, 2025
c5c3469
Add cmd to attempt S3 upload of xcativitylogs
mokagio Oct 16, 2025
d3eae8e
Revert failing S3 upload
mokagio Oct 16, 2025
84431a7
Try printing some metrics
mokagio Oct 16, 2025
0c5321b
Try again to upload to S3
mokagio Oct 16, 2025
d1f7364
Add POST call to Apps Metrics
mokagio Oct 16, 2025
2833cd2
Use correct emojis
mokagio Oct 16, 2025
a510530
Remove a trailing comma that broke the Apps Metric POST
mokagio Oct 16, 2025
02c9b6d
Add script to attempt group metrics upload
mokagio Oct 16, 2025
c82a2f1
Remove unnecessary paths
mokagio Oct 17, 2025
8652b11
Track raw JSONs from xcresulttool
mokagio Oct 17, 2025
7ea4637
Add meta for metrics source
mokagio Oct 17, 2025
28ebcc8
Add branch so we can filter
mokagio Oct 17, 2025
b5f1f7b
Use BUILDKITE_BRANCH if available
mokagio Oct 17, 2025
bd3cc28
Generate and upload build reports, too
mokagio Oct 17, 2025
409e979
Add Buildkite logs subgroups
mokagio Oct 17, 2025
321bb98
Set DerivedData explicitly
mokagio Oct 17, 2025
3745105
Fix typo
mokagio Oct 17, 2025
8349772
Use xcodeproj parameter instead of project
mokagio Oct 17, 2025
918cdc8
Make cURL print its return code
mokagio Oct 17, 2025
3aab84b
Address some RuboCop warnings
mokagio Dec 1, 2025
020ea6b
Add `openssl` gem
mokagio Dec 1, 2025
00f31e0
Run Ruby script for Apps Metrics last
mokagio Dec 1, 2025
ad6f938
Clearer artifacts paths
mokagio Dec 1, 2025
3b712db
Fix incorrect logic in metrics script
mokagio Dec 1, 2025
28325b9
Demo extracting info from the `xcresulttool` JSON
mokagio Dec 1, 2025
f2b1fea
Tweak CI headers
mokagio Dec 1, 2025
bb3d479
Remove `xcactivitylog` steps - Prefer `xcresult`
mokagio Dec 3, 2025
ba257b3
Dump and track raw xcactivity log
mokagio Dec 3, 2025
36d1cb4
Extract logic to track metrics to dedicated script
mokagio Dec 3, 2025
d5e0002
Initial plan
Copilot Dec 3, 2025
49cf2d1
Update track-apple-metrics to take paths as inputs and use buildkite-…
Copilot Dec 3, 2025
7990804
Require path arguments and upload artifacts immediately after generation
Copilot Dec 3, 2025
d21a95f
Remove noisy comment
mokagio Dec 3, 2025
5d7bbf2
Make CI upload xcactivitylog and improve metrics script flexibility (…
mokagio Dec 3, 2025
26f1654
Add Chrome tracer JSON report
mokagio Dec 3, 2025
32efce2
Remove annotation and move Apps Metric POST to script
mokagio Dec 3, 2025
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
2 changes: 2 additions & 0 deletions .buildkite/commands/build-and-test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ fi
echo "--- 📦 Zipping test results"
cd build/results/ && zip -rq Simplenote.xcresult.zip Simplenote.xcresult && cd -

.buildkite/commands/track-apple-metrics.sh "build/results/Simplenote.xcresult" "./DerivedData"

echo "--- 🚦 Report Tests Status"
if [[ $TESTS_EXIT_STATUS -eq 0 ]]; then
echo "Unit Tests seems to have passed (exit code 0). All good 👍"
Expand Down
78 changes: 78 additions & 0 deletions .buildkite/commands/track-apple-metrics.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
#!/bin/bash

set -euo pipefail

XCRESULT_PATH="${1:?Error: xcresult path required as first argument}"
DERIVED_DATA_PATH="${2:?Error: DerivedData path required as second argument}"

echo "--- :xcode: Store raw xcresulttool JSONs"

mkdir -p build/xcresulttool

xcrun xcresulttool get build-results \
--path "$XCRESULT_PATH" \
--format json > build/xcresulttool/xcresulttool-build-results.json

buildkite-agent artifact upload "build/xcresulttool/xcresulttool-build-results.json"

xcrun xcresulttool get test-results tests \
--path "$XCRESULT_PATH" \
--format json > build/xcresulttool/xcresulttool-tests-results.json

buildkite-agent artifact upload "build/xcresulttool/xcresulttool-tests-results.json"

echo "+++ :json: Extract build info from xcresulttool"
jq '{
timestamp: .startTime | strftime("%Y-%m-%d %H:%M:%S"),
duration_ms: ((.endTime - .startTime) * 1000 | round),
status: .status,
errors: .errorCount,
warnings: .warningCount,
analyzer_warnings: .analyzerWarningCount,
warning_breakdown: (.warnings | group_by(.issueType) | map({type: .[0].issueType, count: length}) | sort_by(-.count))
}' build/xcresulttool/xcresulttool-build-results.json

echo "+++ :json: Extract success/fail test count from xcresulttool"
jq '[.. | objects | select(has("result")) | .result] | {passed: map(select(. == "Passed")) | length, failed: map(select(. == "Failed")) | length}' \
build/xcresulttool/xcresulttool-tests-results.json

echo "--- :xcode: Track XCLogParser report"

echo "~~~ Install XCLogParser"

brew install xclogparser

mkdir -p build/xclogparser-reports

echo "~~~ Dump xcactivitylog to JSON"

xclogparser dump \
--xcodeproj Simplenote.xcodeproj \
--derived_data "$DERIVED_DATA_PATH" \
--output build/xclogparser-reports/xcactivitylog-raw.json

buildkite-agent artifact upload "build/xclogparser-reports/xcactivitylog-raw.json"

echo "~~~ Generate JSON report"

xclogparser parse \
--xcodeproj Simplenote.xcodeproj \
--derived_data "$DERIVED_DATA_PATH" \
--reporter json > build/xclogparser-reports/report.json

buildkite-agent artifact upload "build/xclogparser-reports/report.json"

echo "~~~ Generate Chrome tracer report"

xclogparser parse \
--xcodeproj Simplenote.xcodeproj \
--derived_data "$DERIVED_DATA_PATH" \
--reporter chromeTracer > build/xclogparser-reports/build-trace.json

buildkite-agent artifact upload "build/xclogparser-reports/build-trace.json"

echo "--- :arrow_up::ruby: Upload build metrics to Apps Metrics"
# FIXME: Ignoring errors for the time being. Seems like the token has expired...
set +e
ruby .buildkite/commands/upload_metrics.rb
set -e
100 changes: 100 additions & 0 deletions .buildkite/commands/upload_metrics.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# frozen_string_literal: true

require 'json'
require 'time'
require 'open3'
require 'net/http'
require 'uri'
require 'shellwords'

PREFIX = 'simplenote-ios'

XCRESULT_PATH = ARGV[0] || 'build/results/Simplenote.xcresult'

# Hardcoded auth config (or set via ENV)
METRICS_URL = ENV['METRICS_URL'] || 'https://metrics.a8c-ci.services/api/grouped-metrics'
TOKEN = ENV['APPS_METRICS_UPLOAD_TOKEN'] || nil

raise 'No APPS_METRICS_UPLOAD_TOKEN found in environment.' if TOKEN.nil? || TOKEN.empty?

META = [
{ name: 'simplenote-ios-user', value: ENV['USER'] || ENV['USERNAME'] || 'unknown' },
{ name: 'simplenote-ios-project', value: 'simplenote-ios' },
{ name: 'simplenote-ios-environment', value: ENV['CI'] ? 'CI' : 'LOCAL' },
{ name: 'simplenote-ios-architecture', value: `uname -m`.strip },
{ name: 'simplenote-ios-operating-system', value: `uname -s`.strip.downcase },
{ name: 'simplenote-ios-metrics-source', value: 'grouped-metrics' },
{ name: 'simplenote-ios-branch', value: ENV['BUILDKITE_BRANCH'] || `git rev-parse --abbrev-ref HEAD`.strip }
].freeze

# ---------- HELPERS ----------
def run_cmd!(cmd)
out, err, status = Open3.capture3(cmd)
raise "Command failed (#{status.exitstatus}): #{cmd}\n#{err}" unless status.success?

out
end

def to_epoch_ms(str_time)
return nil if str_time.nil? || str_time.empty?

(Time.parse(str_time).to_f * 1000).to_i
rescue ArgumentError => e
puts "`Time.parse` failed with '#{e}'."
nil
end

def dig_count(obj, *path)
v = obj.dig(*path)
return v.to_i if v.is_a?(Numeric) || v.is_a?(String)
return v.length if v.is_a?(Array)

0
end

raw_json = run_cmd!("xcrun xcresulttool get build-results --path #{Shellwords.escape(XCRESULT_PATH)} --format json")
data = JSON.parse(raw_json)

end_time = (data['endTime'] * 1_000).round(0)
start_time = (data['startTime'] * 1_000).round(0)

explicit_fields = {
'action-title' => data['actionTitle'],
'analyzer-warning-count' => data['analyzerWarningCount'],
'end-time-unix-ms' => end_time,
'error-count' => data['errorCount'],
'start-time-unix-ms' => start_time,
'status' => data['status'],
'warning-count' => data['warningCount'],
'build-time' => end_time - start_time
}

metrics_payload = explicit_fields.map do |k, v|
{ name: "#{PREFIX}-#{k}", value: v.to_s }
end

payload = {
meta: META,
metrics: metrics_payload
}

puts 'Will attempt to upload the following JSON:'
puts JSON.pretty_generate(payload)

uri = URI(METRICS_URL)
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = (uri.scheme == 'https')

req = Net::HTTP::Post.new(uri.request_uri)
req['Accept'] = 'application/json'
req['Accept-Charset'] = 'UTF-8'
req['Authorization'] = "Bearer #{TOKEN}"
req['User-Agent'] = 'Xcode/xcresulttool'
req['Content-Type'] = 'application/json'
req.body = JSON.dump(payload)

res = http.request(req)

puts "POST #{METRICS_URL} -> #{res.code}"
puts res.body
exit(res.code.to_i.between?(200, 299) ? 0 : 1)
43 changes: 3 additions & 40 deletions .buildkite/pipeline.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,48 +12,11 @@ env:
# This is the default pipeline – it will build and test the app
steps:

#################
# Run Unit Tests
#################
- label: "🔬 Build and Test"
key: build_and_test
command: ".buildkite/commands/build-and-test.sh"
plugins: [$CI_TOOLKIT_PLUGIN]
artifact_paths:
- "build/results/*"

#################
# Linters
#################
- label: "☢️ Danger - PR Check"
command: danger
key: danger
if: "build.pull_request.id != null"
retry:
manual:
permit_on_passed: true
agents:
queue: "linter"

- label: ":swift: SwiftLint"
command: swiftlint
agents:
queue: "linter"

#################
# Prototype Build
#################
- label: "🛠 Prototype Build"
command: ".buildkite/commands/build-prototype.sh"
plugins: [$CI_TOOLKIT_PLUGIN]
if: "build.pull_request.id != null || build.pull_request.draft"
artifact_paths:
- "build/results/*"

#################
# UI Tests
#################
- label: "🔬 UI Test (Full)"
command: ".buildkite/commands/build-and-ui-test.sh SimplenoteUITests 'iPhone SE (3rd generation)'"
plugins: [$CI_TOOLKIT_PLUGIN]
artifact_paths:
- "build/results/*"
- DerivedData/**/*.xcactivitylog
- build/xclogparser-reports/index.html
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ gem 'fastlane', '~> 2'
gem 'fastlane-plugin-firebase_app_distribution', '~> 0.10'
gem 'fastlane-plugin-sentry', '~> 1.6'
gem 'fastlane-plugin-wpmreleasetoolkit', '~> 13.0'
gem 'openssl'

group :screenshots, optional: true do
gem 'rmagick', '~> 3.2.0'
Expand Down
2 changes: 2 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,7 @@ GEM
faraday (>= 1, < 3)
sawyer (~> 0.9)
open4 (1.3.4)
openssl (3.3.2)
options (2.3.2)
optparse (0.6.0)
os (1.1.4)
Expand Down Expand Up @@ -358,6 +359,7 @@ DEPENDENCIES
fastlane-plugin-firebase_app_distribution (~> 0.10)
fastlane-plugin-sentry (~> 1.6)
fastlane-plugin-wpmreleasetoolkit (~> 13.0)
openssl
rmagick (~> 3.2.0)

BUNDLED WITH
Expand Down
9 changes: 4 additions & 5 deletions fastlane/Fastfile
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ platform :ios do
project: project_path,
scheme: screenshots_scheme,
build_for_testing: true,
derived_data_path: derived_data_directory
derived_data_path: DERIVED_DATA_DIRECTORY
)
end

Expand Down Expand Up @@ -232,7 +232,7 @@ platform :ios do
# folder. This is so that we can parallelize test runs with multiple
# device and locale combinations.
test_without_building: true,
derived_data_path: derived_data_directory,
derived_data_path: DERIVED_DATA_DIRECTORY,

output_directory: screenshots_directory,
clear_previous_screenshots: options.fetch(:clear_previous_screenshots, true),
Expand Down Expand Up @@ -337,6 +337,7 @@ platform :ios do
scheme: 'Simplenote',
device: options[:device] || 'iPhone 14',
output_directory: OUTPUT_DIRECTORY_PATH,
derived_data_path: DERIVED_DATA_DIRECTORY,
reset_simulator: true,
result_bundle: true
)
Expand Down Expand Up @@ -534,9 +535,7 @@ def fastlane_directory
__dir__
end

def derived_data_directory
File.join(fastlane_directory, 'DerivedData')
end
DERIVED_DATA_DIRECTORY = File.join(fastlane_directory, 'DerivedData')

def project_name
'Simplenote.xcodeproj'
Expand Down