mirror of
https://github.com/jayanezj/traccar-client-macos.git
synced 2026-11-05 00:00:45 +01:00
ci: add early arm64 builds and SwiftLint checks (#2)
This commit is contained in:
7 files changed
+312
-1
No files matched your search
@@ -0,0 +1,109 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches: [main]
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ci-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
name: SwiftLint
|
||||
runs-on: macos-15
|
||||
timeout-minutes: 10
|
||||
env:
|
||||
DEVELOPER_DIR: /Applications/Xcode_26.3.app/Contents/Developer
|
||||
steps:
|
||||
- name: Check out source
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Lint Swift sources
|
||||
run: scripts/lint.sh "$RUNNER_TEMP/ci-tools"
|
||||
|
||||
build:
|
||||
name: Build (${{ matrix.name }})
|
||||
runs-on: ${{ matrix.runner }}
|
||||
timeout-minutes: 20
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- name: primary
|
||||
runner: xcode-27
|
||||
developer_dir: /Applications/Xcode_27.1.app/Contents/Developer
|
||||
xcode_version: '27.1'
|
||||
xcode_build: 27A9275
|
||||
sdk_version: '27.0'
|
||||
os_major: '27'
|
||||
- name: macOS 15
|
||||
runner: macos-15
|
||||
developer_dir: /Applications/Xcode_26.3.app/Contents/Developer
|
||||
xcode_version: '26.3'
|
||||
xcode_build: 17C529
|
||||
sdk_version: '26.2'
|
||||
os_major: '15'
|
||||
env:
|
||||
DEVELOPER_DIR: ${{ matrix.developer_dir }}
|
||||
EXPECTED_XCODE_VERSION: ${{ matrix.xcode_version }}
|
||||
EXPECTED_XCODE_BUILD: ${{ matrix.xcode_build }}
|
||||
EXPECTED_SDK_VERSION: ${{ matrix.sdk_version }}
|
||||
EXPECTED_OS_MAJOR: ${{ matrix.os_major }}
|
||||
steps:
|
||||
- name: Check out source
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Verify runner and toolchain
|
||||
run: |
|
||||
mkdir -p "$RUNNER_TEMP/ci-diagnostics"
|
||||
scripts/verify-toolchain.sh "$EXPECTED_XCODE_VERSION" "$EXPECTED_XCODE_BUILD" \
|
||||
"$EXPECTED_SDK_VERSION" "$EXPECTED_OS_MAJOR" \
|
||||
2>&1 | tee "$RUNNER_TEMP/ci-diagnostics/toolchain.log"
|
||||
- name: Build Debug
|
||||
run: |
|
||||
scripts/build.sh "$RUNNER_TEMP/traccar-ci" Debug \
|
||||
2>&1 | tee "$RUNNER_TEMP/ci-diagnostics/build-debug.log"
|
||||
- name: Build Release
|
||||
run: |
|
||||
scripts/build.sh "$RUNNER_TEMP/traccar-ci" Release \
|
||||
2>&1 | tee "$RUNNER_TEMP/ci-diagnostics/build-release.log"
|
||||
- name: Verify application binaries
|
||||
run: scripts/verify-build.sh "$RUNNER_TEMP/traccar-ci"
|
||||
- name: Sanitize failure diagnostics
|
||||
if: failure()
|
||||
run: |
|
||||
python3 - <<'PY'
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
root = Path(os.environ['RUNNER_TEMP'])
|
||||
output = root / 'ci-diagnostics-sanitized'
|
||||
output.mkdir(exist_ok=True)
|
||||
replacements = [(os.environ[key], f'<{key}>')
|
||||
for key in ('GITHUB_WORKSPACE', 'RUNNER_TEMP', 'HOME')]
|
||||
for log in (root / 'ci-diagnostics').glob('*.log'):
|
||||
content = log.read_text(errors='replace')
|
||||
for value, replacement in replacements:
|
||||
content = content.replace(value, replacement)
|
||||
(output / log.name).write_text(content)
|
||||
PY
|
||||
- name: Upload failure diagnostics
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||
with:
|
||||
name: build-diagnostics-${{ matrix.runner }}
|
||||
path: ${{ runner.temp }}/ci-diagnostics-sanitized/*.log
|
||||
if-no-files-found: warn
|
||||
retention-days: 7
|
||||
@@ -0,0 +1,6 @@
|
||||
included:
|
||||
- TraccarClient
|
||||
|
||||
# Match Swift's multiline collection style without disabling the default rules.
|
||||
trailing_comma:
|
||||
mandatory_comma: true
|
||||
@@ -32,7 +32,66 @@ The app uses ad hoc signing, with no Apple developer account, provisioning profi
|
||||
4. Open About and check the version, unofficial-client statement and Apache-2.0 license information.
|
||||
5. Quit from the menu. The process should terminate.
|
||||
|
||||
No location permission request or server connection is part of this shell. Real-Mac UI checks remain distinct from automated compilation. Early CI is tracked in [issue #2](https://github.com/jayanezj/traccar-client-macos/issues/2), before subsequent functionality.
|
||||
No location permission request or server connection is part of this shell. Real-Mac UI checks remain distinct from automated compilation.
|
||||
|
||||
## Continuous integration
|
||||
|
||||
The workflow in `.github/workflows/ci.yml` runs on pull requests, pushes to `main` and manual dispatch. It exposes three checks:
|
||||
|
||||
| Check | Runner | Toolchain | Work |
|
||||
| --- | --- | --- | --- |
|
||||
| SwiftLint | `macos-15` arm64 | Xcode 26.3; SwiftLint 0.65.1 | Strict lint, including warnings |
|
||||
| Build (primary) | `xcode-27` arm64 | Xcode 27.1, build 27A9275; macOS SDK 27.0 | Debug and Release |
|
||||
| Build (macOS 15) | `macos-15` arm64 | Xcode 26.3, build 17C529; macOS SDK 26.2 | Debug and Release |
|
||||
|
||||
Both build jobs verify the host architecture, OS major version, selected Xcode/build and SDK before compiling. They also verify arm64 binaries, deployment target 15.0 and application signatures. A missing or unexpected toolchain fails the check. The `xcode-27` runner image is a public preview, accepted for early CI; Xcode 27.1 build 27A9275 itself is an Apple final release. Review the runner's status before automating releases.
|
||||
|
||||
This stage performs lint and compilation only: there is no test target yet. Configuration tests start with [issue #3](https://github.com/jayanezj/traccar-client-macos/issues/3); subsequent features extend this workflow with meaningful tests. Builds do not validate location permissions, sleep behaviour or Gatekeeper.
|
||||
|
||||
### Local checks before pushing
|
||||
|
||||
Run all local checks with one command, supplying an absolute validation directory outside the repository, preferably on your external development volume:
|
||||
|
||||
```sh
|
||||
scripts/check-local.sh /path/to/external/local-ci
|
||||
```
|
||||
|
||||
Replace the example with your actual path. The default `primary` profile requires macOS 27 arm64, Xcode 27.1 build 27A9275 and SDK 27.0. The script uses the Xcode selected by `xcode-select`, or the `DEVELOPER_DIR` environment variable if set. To select a particular installation without changing the system selection:
|
||||
|
||||
```sh
|
||||
DEVELOPER_DIR=/path/to/Xcode.app/Contents/Developer \
|
||||
scripts/check-local.sh /path/to/external/local-ci
|
||||
```
|
||||
|
||||
The command checks the toolchain, shell syntax and whitespace in staged/unstaged tracked changes, runs strict SwiftLint, builds Debug and Release, then verifies both binaries' architecture, deployment target and signatures. It stops at the first failure and returns a nonzero exit code. Tools are cached under `tools/` and incremental build products under `build/` in the supplied directory. The toolchain, lint, build and binary verification scripts are shared with CI.
|
||||
|
||||
On a separate macOS 15 arm64 environment with Xcode 26.3 build 17C529 and SDK 26.2, select the minimum-system profile explicitly:
|
||||
|
||||
```sh
|
||||
scripts/check-local.sh /path/to/external/local-ci-macos15 macos15
|
||||
```
|
||||
|
||||
Profiles verify the actual machine and toolchain; they do not emulate another macOS version. This command does not reproduce GitHub's clean hosted environment, triggers, permissions or artifact uploads. It does not launch the application; perform the manual smoke check separately. A successful local run does not establish hosted CI or minimum-OS runtime coverage.
|
||||
|
||||
### Individual checks
|
||||
|
||||
Run the same lint locally, using an absolute tools directory outside the repository:
|
||||
|
||||
```sh
|
||||
scripts/lint.sh /path/to/external/tools-directory
|
||||
```
|
||||
|
||||
The script downloads the official portable SwiftLint release, verifies its pinned SHA-256 and checks the executable version. `.swiftlint.yml` applies the default rules to application sources, with mandatory trailing commas in multiline collections. No baseline or automatic fixes are applied. SwiftLint is a development tool, not an application package dependency.
|
||||
|
||||
For a matching machine, reproduce the main toolchain check with:
|
||||
|
||||
```sh
|
||||
scripts/verify-toolchain.sh 27.1 27A9275 27.0 27
|
||||
```
|
||||
|
||||
CI uses read-only repository permissions, Actions pinned to full commit SHAs, bounded timeouts and cancellation of superseded runs. Checkout does not persist credentials. It requires no production secrets or Apple account and does not run the application or send positions. Failed builds retain only text diagnostics, with workspace, temporary and home paths replaced, for seven days; app bundles and build caches are not uploaded.
|
||||
|
||||
Swift analysis in SonarQube requires Developer Edition, so SonarQube Community is not a required check. CycloneDX SBOM publication to Dependency-Track is deferred until the application has external dependencies; include it in release automation when applicable.
|
||||
|
||||
## Artwork
|
||||
|
||||
|
||||
Executable
+55
@@ -0,0 +1,55 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
usage() {
|
||||
echo "Usage: scripts/check-local.sh /absolute/path/to/validation-directory [primary|macos15]"
|
||||
}
|
||||
|
||||
if [[ "${1:-}" == --help || "${1:-}" == -h ]]; then
|
||||
usage
|
||||
exit 0
|
||||
fi
|
||||
if [[ $# -lt 1 || $# -gt 2 ]]; then
|
||||
usage >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
validation_root="$1"
|
||||
profile="${2:-primary}"
|
||||
case "$validation_root" in
|
||||
/*) ;;
|
||||
*) echo "The validation directory must be an absolute path." >&2; exit 2 ;;
|
||||
esac
|
||||
case "$profile" in
|
||||
primary) toolchain=(27.1 27A9275 27.0 27) ;;
|
||||
macos15) toolchain=(26.3 17C529 26.2 15) ;;
|
||||
*) echo "Profile must be primary or macos15." >&2; exit 2 ;;
|
||||
esac
|
||||
|
||||
repo_root="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
cd "$repo_root"
|
||||
trap 'status=$?; echo "Local checks failed (exit $status)." >&2; exit "$status"' ERR
|
||||
|
||||
echo "Checking toolchain ($profile)..."
|
||||
scripts/verify-toolchain.sh "${toolchain[@]}"
|
||||
|
||||
echo "Checking shell syntax and Git whitespace..."
|
||||
for script in scripts/*.sh; do
|
||||
bash -n "$script"
|
||||
done
|
||||
git diff --check
|
||||
git diff --cached --check
|
||||
|
||||
echo "Running SwiftLint..."
|
||||
scripts/lint.sh "$validation_root/tools"
|
||||
|
||||
for configuration in Debug Release; do
|
||||
echo "Building $configuration..."
|
||||
scripts/build.sh "$validation_root/build" "$configuration"
|
||||
done
|
||||
|
||||
echo "Checking application binaries and signatures..."
|
||||
scripts/verify-build.sh "$validation_root/build"
|
||||
|
||||
echo "Local checks passed ($profile): lint, Debug, Release, arm64, macOS 15.0 minimum and signatures."
|
||||
echo "Hosted workflow execution and manual application checks remain separate."
|
||||
Executable
+38
@@ -0,0 +1,38 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
tools_root="${1:?Usage: scripts/lint.sh /absolute/path/to/tools-directory}"
|
||||
case "$tools_root" in
|
||||
/*) ;;
|
||||
*) echo "The tools directory must be an absolute path." >&2; exit 2 ;;
|
||||
esac
|
||||
|
||||
repo_root="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
version="0.65.1"
|
||||
checksum="c1e429b0599cf1b516f369a2d9ec04eaf0e436f3c12b637df8851fa52ff694d0"
|
||||
install_root="$tools_root/swiftlint-$version"
|
||||
binary="$install_root/swiftlint"
|
||||
|
||||
if [[ ! -x "$binary" ]]; then
|
||||
mkdir -p "$tools_root"
|
||||
download_root="$(mktemp -d "$tools_root/swiftlint-download.XXXXXX")"
|
||||
trap 'rm -rf "$download_root"' EXIT
|
||||
curl --fail --location --silent --show-error --retry 3 \
|
||||
--proto '=https' --proto-redir '=https' \
|
||||
"https://github.com/realm/SwiftLint/releases/download/$version/portable_swiftlint.zip" \
|
||||
--output "$download_root/portable_swiftlint.zip"
|
||||
printf '%s %s\n' "$checksum" "$download_root/portable_swiftlint.zip" | shasum -a 256 -c -
|
||||
mkdir "$download_root/extracted"
|
||||
unzip -q "$download_root/portable_swiftlint.zip" -d "$download_root/extracted"
|
||||
mkdir -p "$install_root"
|
||||
cp "$download_root/extracted/swiftlint" "$binary"
|
||||
chmod +x "$binary"
|
||||
fi
|
||||
|
||||
if [[ "$("$binary" version)" != "$version" ]]; then
|
||||
echo "Expected SwiftLint $version. Remove $install_root and retry." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cd "$repo_root"
|
||||
"$binary" lint --strict --no-cache --config .swiftlint.yml
|
||||
Executable
+17
@@ -0,0 +1,17 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
build_root="${1:?Usage: scripts/verify-build.sh /absolute/path/to/build-directory}"
|
||||
case "$build_root" in
|
||||
/*) ;;
|
||||
*) echo "The build directory must be an absolute path." >&2; exit 2 ;;
|
||||
esac
|
||||
|
||||
for configuration in Debug Release; do
|
||||
app="$build_root/DerivedData/Build/Products/$configuration/Traccar Client macOS.app"
|
||||
executable="$app/Contents/MacOS/Traccar Client macOS"
|
||||
echo "Verifying $configuration..."
|
||||
test "$(lipo -archs "$executable")" = arm64
|
||||
test "$(xcrun vtool -show-build "$executable" | awk '/minos/ {print $2}')" = 15.0
|
||||
codesign --verify --strict "$app"
|
||||
done
|
||||
Executable
+27
@@ -0,0 +1,27 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
expected_version="${1:?Usage: scripts/verify-toolchain.sh XCODE_VERSION XCODE_BUILD SDK_VERSION OS_MAJOR}"
|
||||
expected_build="${2:?Missing Xcode build}"
|
||||
expected_sdk="${3:?Missing macOS SDK version}"
|
||||
expected_os="${4:?Missing macOS major version}"
|
||||
|
||||
[[ "$(uname -m)" == arm64 ]] || { echo "An arm64 host is required." >&2; exit 1; }
|
||||
[[ "$(sw_vers -productVersion | cut -d . -f 1)" == "$expected_os" ]] || {
|
||||
echo "Expected macOS $expected_os." >&2; exit 1;
|
||||
}
|
||||
|
||||
actual_xcode="$(xcodebuild -version)"
|
||||
printf '%s\n' "$actual_xcode"
|
||||
[[ "$actual_xcode" == "$(printf 'Xcode %s\nBuild version %s' "$expected_version" "$expected_build")" ]] || {
|
||||
echo "Unexpected Xcode version/build." >&2; exit 1;
|
||||
}
|
||||
|
||||
actual_sdk="$(xcrun --sdk macosx --show-sdk-version)"
|
||||
[[ "$actual_sdk" == "$expected_sdk" ]] || {
|
||||
echo "Expected macOS SDK $expected_sdk; found $actual_sdk." >&2; exit 1;
|
||||
}
|
||||
|
||||
sw_vers
|
||||
xcrun swift --version
|
||||
printf 'Architecture: %s\nmacOS SDK: %s\n' "$(uname -m)" "$actual_sdk"
|
||||
Reference in new issue
Block a user