From e22ae29b3f87d670d9cbdbe03584dd99c54c6de7 Mon Sep 17 00:00:00 2001 From: Brent Perteet Date: Mon, 24 Aug 2026 17:05:43 -0500 Subject: [PATCH] ci: add macOS app validation pipeline --- Jenkinsfile | 48 ++++++++++++++++++++++++++++++++ scripts/ci/build-ios-unsigned.sh | 40 ++++++++++++++++++++++++++ scripts/ci/test.sh | 28 +++++++++++++++++++ scripts/ci/verify-macos-agent.sh | 33 ++++++++++++++++++++++ 4 files changed, 149 insertions(+) create mode 100644 Jenkinsfile create mode 100755 scripts/ci/build-ios-unsigned.sh create mode 100755 scripts/ci/test.sh create mode 100755 scripts/ci/verify-macos-agent.sh diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 0000000..79a2cb3 --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,48 @@ +pipeline { + agent { label 'um-trace' } + + options { + buildDiscarder(logRotator(numToKeepStr: '20')) + disableConcurrentBuilds() + timeout(time: 45, unit: 'MINUTES') + } + + environment { + DEVELOPER_DIR = '/Applications/Xcode.app/Contents/Developer' + DOTNET_ROOT = '/Users/brent/.dotnet' + DOTNET_CLI_TELEMETRY_OPTOUT = '1' + DOTNET_NOLOGO = '1' + NUGET_XMLDOC_MODE = 'skip' + PATH = "/Users/brent/.dotnet:/opt/homebrew/bin:${env.PATH}" + } + + stages { + stage('Toolchain') { + steps { + sh './scripts/ci/verify-macos-agent.sh' + } + } + + stage('Tests') { + steps { + sh './scripts/ci/test.sh' + } + } + + stage('iOS Build (Unsigned)') { + steps { + sh './scripts/ci/build-ios-unsigned.sh' + } + } + } + + post { + always { + archiveArtifacts( + artifacts: 'artifacts/**/*', + allowEmptyArchive: true, + fingerprint: true + ) + } + } +} diff --git a/scripts/ci/build-ios-unsigned.sh b/scripts/ci/build-ios-unsigned.sh new file mode 100755 index 0000000..99caecb --- /dev/null +++ b/scripts/ci/build-ios-unsigned.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +set -euo pipefail + +repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +dotnet_bin="${DOTNET_BIN:-/Users/brent/.dotnet/dotnet}" +developer_dir="${DEVELOPER_DIR:-/Applications/Xcode.app/Contents/Developer}" +project="${repo_dir}/FieldLogger/FieldLogger.csproj" +runtime_identifier="${IOS_RUNTIME:-ios-arm64}" +nuget_cache="${UM_TRACE_NUGET_CACHE:-${TMPDIR:-/tmp}/um-trace-ci-nuget}" + +export DEVELOPER_DIR="${developer_dir}" +export NUGET_HTTP_CACHE_PATH="${NUGET_HTTP_CACHE_PATH:-${nuget_cache}/http-cache}" + +mkdir -p "${NUGET_HTTP_CACHE_PATH}" + +"${dotnet_bin}" restore "${project}" \ + -p:TargetFrameworks=net9.0-ios \ + -p:RuntimeIdentifier="${runtime_identifier}" \ + -p:NuGetAudit=false + +# A narrowed restore of the MAUI head can rewrite the referenced library's +# assets file, so restore the headless sync library explicitly as well. +"${dotnet_bin}" restore "${repo_dir}/src/FieldLogger.Sync/FieldLogger.Sync.csproj" \ + -p:NuGetAudit=false + +"${dotnet_bin}" clean "${project}" \ + --framework net9.0-ios \ + --configuration Debug \ + -p:TargetFrameworks=net9.0-ios \ + -p:RuntimeIdentifier="${runtime_identifier}" + +"${dotnet_bin}" build "${project}" \ + --framework net9.0-ios \ + --configuration Debug \ + --runtime "${runtime_identifier}" \ + -p:TargetFrameworks=net9.0-ios \ + -p:EnableCodeSigning=false \ + -p:CodesignKey= \ + -p:CodesignProvision= \ + --no-restore diff --git a/scripts/ci/test.sh b/scripts/ci/test.sh new file mode 100755 index 0000000..8aba7ec --- /dev/null +++ b/scripts/ci/test.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +set -euo pipefail + +repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +dotnet_bin="${DOTNET_TEST_BIN:-/usr/local/share/dotnet/dotnet}" +results_dir="${repo_dir}/artifacts/test-results" +nuget_cache="${UM_TRACE_NUGET_CACHE:-${TMPDIR:-/tmp}/um-trace-ci-nuget}" + +mkdir -p "${results_dir}" "${nuget_cache}/http-cache" + +export NUGET_HTTP_CACHE_PATH="${NUGET_HTTP_CACHE_PATH:-${nuget_cache}/http-cache}" + +test_projects=( + "tests/FieldLogger.Sync.Tests/FieldLogger.Sync.Tests.csproj" + "tests/FieldLogger.Tests/FieldLogger.Tests.csproj" + "tests/IfLoc.Sim.Tests/IfLoc.Sim.Tests.csproj" +) + +for project in "${test_projects[@]}"; do + project_name="$(basename "${project}" .csproj)" + DOTNET_ROOT=/usr/local/share/dotnet "${dotnet_bin}" restore "${repo_dir}/${project}" \ + -p:NuGetAudit=false + DOTNET_ROOT=/usr/local/share/dotnet "${dotnet_bin}" test "${repo_dir}/${project}" \ + --configuration Release \ + --no-restore \ + --logger "trx;LogFileName=${project_name}.trx" \ + --results-directory "${results_dir}" +done diff --git a/scripts/ci/verify-macos-agent.sh b/scripts/ci/verify-macos-agent.sh new file mode 100755 index 0000000..9f7167b --- /dev/null +++ b/scripts/ci/verify-macos-agent.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash +set -euo pipefail + +dotnet_bin="${DOTNET_BIN:-/Users/brent/.dotnet/dotnet}" +test_dotnet_bin="${DOTNET_TEST_BIN:-/usr/local/share/dotnet/dotnet}" +developer_dir="${DEVELOPER_DIR:-/Applications/Xcode.app/Contents/Developer}" + +if [[ ! -x "${dotnet_bin}" ]]; then + echo "Missing Xcode 26 build SDK at ${dotnet_bin}." >&2 + exit 2 +fi + +if [[ ! -x "${test_dotnet_bin}" ]]; then + echo "Missing .NET 9 test SDK at ${test_dotnet_bin}." >&2 + exit 2 +fi + +if [[ ! -d "${developer_dir}" ]]; then + echo "Missing Xcode developer directory at ${developer_dir}." >&2 + exit 2 +fi + +export DEVELOPER_DIR="${developer_dir}" + +java -version +xcodebuild -version +"${dotnet_bin}" --info +"${dotnet_bin}" workload list +DOTNET_ROOT=/usr/local/share/dotnet "${test_dotnet_bin}" --info + +# Signing is not required for the unsigned compile build, but report what will be +# available to later approval-gated device and TestFlight stages. +security find-identity -v -p codesigning || true