Skip to content

Commit 27853b5

Browse files
committed
misc: update ci workflows
1 parent fa51b49 commit 27853b5

File tree

18 files changed

+647
-521
lines changed

18 files changed

+647
-521
lines changed
File renamed without changes.
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
name: Build XCFramework
2+
description: Build XCFramework for iOS and iOS Simulator
3+
4+
inputs:
5+
project_name:
6+
description: Name of the Xcode project or scheme
7+
required: true
8+
9+
outputs:
10+
xcframework_path:
11+
description: Final path to the generated XCFramework
12+
value: ${{ steps.build.outputs.xcframework_path }}
13+
14+
runs:
15+
using: "composite"
16+
steps:
17+
- name: Build XCFramework
18+
id: build
19+
shell: bash
20+
run: |
21+
set -euo pipefail
22+
23+
PROJECT_NAME="${{ inputs.project_name }}"
24+
BUILD_DIR="./build"
25+
26+
echo "🛠️ Building XCFramework..."
27+
28+
xcodebuild archive \
29+
-scheme "$PROJECT_NAME" \
30+
-configuration Release \
31+
-destination 'generic/platform=iOS Simulator' \
32+
-archivePath "$BUILD_DIR/${PROJECT_NAME}.framework-iphonesimulator.xcarchive" \
33+
SKIP_INSTALL=NO \
34+
BUILD_LIBRARIES_FOR_DISTRIBUTION=YES | xcbeautify
35+
36+
xcodebuild archive \
37+
-scheme "$PROJECT_NAME" \
38+
-configuration Release \
39+
-destination 'generic/platform=iOS' \
40+
-archivePath "$BUILD_DIR/${PROJECT_NAME}.framework-iphoneos.xcarchive" \
41+
SKIP_INSTALL=NO \
42+
BUILD_LIBRARIES_FOR_DISTRIBUTION=YES | xcbeautify
43+
44+
XCFRAMEWORK_PATH="$BUILD_DIR/${PROJECT_NAME}.xcframework"
45+
46+
xcodebuild -create-xcframework \
47+
-framework "$BUILD_DIR/${PROJECT_NAME}.framework-iphonesimulator.xcarchive/Products/Library/Frameworks/${PROJECT_NAME}.framework" \
48+
-framework "$BUILD_DIR/${PROJECT_NAME}.framework-iphoneos.xcarchive/Products/Library/Frameworks/${PROJECT_NAME}.framework" \
49+
-output "$XCFRAMEWORK_PATH"
50+
51+
echo "✅ XCFramework built successfully"
52+
echo "xcframework_path=$XCFRAMEWORK_PATH" >> "$GITHUB_OUTPUT"
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
name: Get Project Version
2+
description: Extracts MARKETING_VERSION from a .pbxproj file
3+
4+
inputs:
5+
project_name:
6+
description: Name of the Xcode project (without .xcodeproj)
7+
required: false
8+
pbxproj_path:
9+
description: Path to the project.pbxproj file (overrides project_name if provided)
10+
required: false
11+
12+
outputs:
13+
version:
14+
description: The extracted project version
15+
value: ${{ steps.extract.outputs.version }}
16+
17+
runs:
18+
using: "composite"
19+
steps:
20+
- name: Extract current project version
21+
id: extract
22+
shell: bash
23+
run: |
24+
set -euo pipefail
25+
26+
# Determine the pbxproj path
27+
if [[ -n "${{ inputs.pbxproj_path }}" ]]; then
28+
PBXPROJ="${{ inputs.pbxproj_path }}"
29+
elif [[ -n "${{ inputs.project_name }}" ]]; then
30+
PBXPROJ="${{ inputs.project_name }}.xcodeproj/project.pbxproj"
31+
else
32+
echo "❌ Either 'project_name' or 'pbxproj_path' must be provided."
33+
exit 1
34+
fi
35+
36+
# Check if the pbxproj file exists
37+
if [[ ! -f "$PBXPROJ" ]]; then
38+
echo "❌ Project file not found: $PBXPROJ"
39+
exit 1
40+
fi
41+
42+
echo "📦 Extracting current MARKETING_VERSION from $PBXPROJ..."
43+
VERSION=$(grep -m1 'MARKETING_VERSION =' "$PBXPROJ" | sed -E 's/.*MARKETING_VERSION = ([^;]+);/\1/' | xargs)
44+
echo "🔢 Current version: $VERSION"
45+
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
name: Install Dependencies
2+
description: Checks for Homebrew and installs any missing CLI tools and Ruby gems
3+
4+
inputs:
5+
tools:
6+
description: Space-separated list of tools to check and install via Homebrew
7+
required: false
8+
gems:
9+
description: Space-separated list of gems to check and install via gem
10+
required: false
11+
12+
runs:
13+
using: "composite"
14+
steps:
15+
- name: Install Dependencies
16+
shell: bash
17+
run: |
18+
set -euo pipefail
19+
20+
if [ -n "${{ inputs.tools }}" ]; then
21+
echo "🔍 Checking for Homebrew..."
22+
if ! command -v brew >/dev/null; then
23+
echo "❌ Homebrew is required but not installed. Aborting."
24+
exit 1
25+
fi
26+
27+
echo "🔧 Installing missing brew tools..."
28+
for tool in ${{ inputs.tools }}; do
29+
if command -v "$tool" >/dev/null; then
30+
echo "✅ $tool is already installed."
31+
else
32+
echo "📦 Installing $tool via brew..."
33+
brew install "$tool"
34+
fi
35+
done
36+
fi
37+
38+
if [ -n "${{ inputs.gems }}" ]; then
39+
echo "🔧 Installing missing gems..."
40+
for gem in ${{ inputs.gems }}; do
41+
if gem list -i "$gem" >/dev/null; then
42+
echo "✅ $gem gem is already installed."
43+
else
44+
echo "💎 Installing $gem via gem..."
45+
gem install "$gem"
46+
fi
47+
done
48+
fi
49+
50+
echo "✅ All dependencies are ready."
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
name: Package XCFramework and LICENSE
2+
description: Zips the built XCFramework and LICENSE into a versioned release artifact
3+
4+
inputs:
5+
package_name:
6+
description: The name of the package (e.g., MyLibrary-1.0.0)
7+
required: true
8+
xcframework_path:
9+
description: The path to the built .xcframework
10+
required: true
11+
license_path:
12+
description: The path to the LICENSE file
13+
required: true
14+
15+
outputs:
16+
zip_name:
17+
description: The name of the created zip file
18+
value: ${{ steps.package.outputs.zip_name }}
19+
20+
runs:
21+
using: "composite"
22+
steps:
23+
- id: package
24+
shell: bash
25+
run: |
26+
set -euo pipefail
27+
ZIP_NAME="${{ inputs.package_name }}.zip"
28+
mkdir -p release
29+
cp -R "${{ inputs.xcframework_path }}" release/
30+
cp "${{ inputs.license_path }}" release/
31+
cd release
32+
zip -r "../$ZIP_NAME" .
33+
echo "✅ Packaged XCFramework and LICENSE into $ZIP_NAME"
34+
echo "zip_name=$ZIP_NAME" >> "$GITHUB_OUTPUT"
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
name: Set Xcode Version
2+
description: Selects the desired Xcode version using xcode-select.
3+
inputs:
4+
xcode-version:
5+
description: The Xcode version to select (e.g., 16.4)
6+
required: true
7+
runs:
8+
using: 'composite'
9+
steps:
10+
- run: |
11+
set -e
12+
echo "Setting Xcode version to ${{ inputs.xcode-version }}..."
13+
if ! sudo xcode-select -s /Applications/Xcode_${{ inputs.xcode-version }}.app/Contents/Developer; then
14+
echo "❌ Failed to select Xcode ${{ inputs.xcode-version }}. Listing available Xcodes:"
15+
ls /Applications | grep Xcode
16+
exit 1
17+
fi
18+
shell: bash
File renamed without changes.

.github/workflows/ci.yml

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
pull_request:
8+
types: [opened, synchronize, reopened]
9+
10+
permissions:
11+
contents: read
12+
pull-requests: write # For PR comments
13+
14+
env:
15+
PROJECT_NAME: OSBarcodeLib
16+
SCHEME_NAME: OSBarcodeLib
17+
XCODEPROJ_PATH: OSBarcodeLib.xcodeproj
18+
XCODE_VERSION: 16.4
19+
DESTINATION: 'platform=iOS Simulator,OS=latest,name=iPhone 16'
20+
COVERAGE_TARGET_FILTER: OSBarcodeLib
21+
BUILD_REPORTS_DIR: build/reports
22+
SONAR_REPORTS_DIR: sonar-reports
23+
24+
jobs:
25+
test:
26+
name: Run Tests
27+
runs-on: macos-15
28+
29+
steps:
30+
- name: Checkout code
31+
uses: actions/checkout@v4
32+
33+
- name: Install Dependencies
34+
uses: ./.github/actions/install-dependencies
35+
with:
36+
tools: swiftlint xcbeautify
37+
38+
- name: Set Xcode version
39+
uses: ./.github/actions/set-xcode-version
40+
with:
41+
xcode-version: ${{ env.XCODE_VERSION }}
42+
43+
- name: Run Unit Tests
44+
id: unit_tests
45+
env:
46+
SCHEME_NAME: ${{ env.SCHEME_NAME }}
47+
XCODEPROJ_PATH: ${{ env.XCODEPROJ_PATH }}
48+
IOS_SIMULATOR_DEVICE: ${{ env.IOS_SIMULATOR_DEVICE }}
49+
DESTINATION: ${{ env.DESTINATION }}
50+
run: |
51+
set -euo pipefail
52+
XCRESULT_NAME="TestResults.xcresult"
53+
mkdir -p "$BUILD_REPORTS_DIR"
54+
xcodebuild test \
55+
-project "$XCODEPROJ_PATH" \
56+
-scheme "$SCHEME_NAME" \
57+
-destination "$DESTINATION" \
58+
-configuration Debug \
59+
-enableCodeCoverage YES \
60+
-resultBundlePath "$XCRESULT_NAME" \
61+
SKIP_SCRIPT_PHASES=YES \
62+
CODE_SIGNING_ALLOWED=NO | xcbeautify --report junit --report-path "$BUILD_REPORTS_DIR"
63+
echo "xcresult_name=$XCRESULT_NAME" >> "$GITHUB_OUTPUT"
64+
65+
- name: Generate Code Coverage Report for SonarQube
66+
continue-on-error: true
67+
env:
68+
XCRESULT_NAME: ${{ steps.unit_tests.outputs.xcresult_name }}
69+
run: |
70+
set -euo pipefail
71+
echo "🔍 Generating SonarQube coverage report..."
72+
73+
if [ ! -d "$XCRESULT_NAME" ]; then
74+
echo "⚠️ $XCRESULT_NAME not found. Skipping coverage report generation."
75+
exit 0
76+
fi
77+
78+
mkdir -p ${{ env.SONAR_REPORTS_DIR }}
79+
80+
echo "📦 Downloading coverage converter script..."
81+
curl -sSL https://raw.githubusercontent.com/SonarSource/sonar-scanning-examples/master/swift-coverage/swift-coverage-example/xccov-to-sonarqube-generic.sh -o xccov-to-sonarqube-generic.sh
82+
chmod +x xccov-to-sonarqube-generic.sh
83+
84+
echo "📝 Running coverage converter..."
85+
./xccov-to-sonarqube-generic.sh TestResults.xcresult > ${{ env.SONAR_REPORTS_DIR }}/sonarqube-generic-coverage.xml
86+
echo "✅ SonarQube coverage report generated successfully"
87+
88+
- name: Run SwiftLint for SonarQube
89+
run: |
90+
set -euo pipefail
91+
echo "🔍 Running SwiftLint..."
92+
mkdir -p ${{ env.SONAR_REPORTS_DIR }}
93+
swiftlint --reporter checkstyle > "${{ env.SONAR_REPORTS_DIR }}/swiftlint.xml" || {
94+
echo "⚠️ SwiftLint finished with issues."
95+
exit 0
96+
}
97+
echo "✅ SwiftLint report generated successfully"
98+
99+
- name: Setup SonarQube Scanner
100+
uses: warchant/setup-sonar-scanner@v8
101+
102+
- name: Send to SonarCloud
103+
id: sonarcloud
104+
continue-on-error: true
105+
run: |
106+
set -euo pipefail
107+
if [ -z "${{ secrets.SONAR_TOKEN }}" ]; then
108+
echo "⚠️ SONAR_TOKEN secret is not set. Skipping SonarCloud analysis."
109+
exit 0
110+
fi
111+
if [ -f "sonar-project.properties" ]; then
112+
echo "🔍 Sending results to SonarCloud..."
113+
echo "📦 Commit: ${{ github.sha }}"
114+
if [ "${{ github.ref_name }}" = "main" ]; then
115+
echo "🌟 Analyzing main branch"
116+
sonar-scanner
117+
else
118+
echo "🌿 Analyzing feature branch: ${{ github.ref_name }}"
119+
sonar-scanner -Dsonar.branch.name="${{ github.ref_name }}"
120+
fi
121+
else
122+
echo "⚠️ sonar-project.properties not found, skipping SonarCloud"
123+
fi
124+
env:
125+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
126+
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
127+
128+
- name: Upload Test Results
129+
uses: actions/upload-artifact@v4
130+
if: always()
131+
with:
132+
name: test-results
133+
path: |
134+
${{ steps.unit_tests.outputs.xcresult_name }}
135+
${{ env.SONAR_REPORTS_DIR }}
136+
${{ env.BUILD_REPORTS_DIR }}
137+
138+
- name: Comment Test Results
139+
if: github.event_name == 'pull_request'
140+
uses: actions/github-script@v7
141+
env:
142+
XCRESULT_NAME: ${{ steps.unit_tests.outputs.xcresult_name }}
143+
COVERAGE_TARGET_FILTER: ${{ env.COVERAGE_TARGET_FILTER }}
144+
with:
145+
script: |
146+
const { execSync } = require('child_process');
147+
const fs = require('fs');
148+
149+
console.log('📝 Starting to comment test results...');
150+
let coveragePercentage = 'N/A';
151+
try {
152+
const xcresultName = process.env.XCRESULT_NAME;
153+
const coverageTarget = process.env.COVERAGE_TARGET_FILTER;
154+
console.log(`Checking result file: ${xcresultName}`);
155+
if (fs.existsSync(xcresultName)) {
156+
console.log('Result file found. Calculating coverage...');
157+
const output = execSync(`xcrun xccov view --report "${xcresultName}"`).toString();
158+
const match = output.match(new RegExp(`${coverageTarget}.*?([0-9]+\\.[0-9]+%)`));
159+
if (match && match[1]) {
160+
coveragePercentage = match[1];
161+
console.log(`Coverage found: ${coveragePercentage}`);
162+
} else {
163+
console.log('Coverage not found in report.');
164+
}
165+
} else {
166+
console.log('Result file not found.');
167+
}
168+
} catch (e) {
169+
console.error('Error calculating coverage:', e);
170+
coveragePercentage = 'N/A';
171+
}
172+
173+
console.log('Commenting on PR with test results and coverage...');
174+
await github.rest.issues.createComment({
175+
issue_number: context.issue.number,
176+
owner: context.repo.owner,
177+
repo: context.repo.repo,
178+
body: `✅ **Tests**: All passed\n📊 **Coverage**: ${coveragePercentage}`
179+
});
180+
console.log('Comment sent successfully.');

0 commit comments

Comments
 (0)