Guide

Ajouter un Mac comme agent Jenkins pour vos builds iOS

Pour ajouter un Mac à Jenkins, créez un nœud permanent qui se connecte au contrôleur. Sur le Mac, installez Java 21 et lancez agent.jar avec l’option -webSocket. Enveloppez cette commande dans un LaunchAgent et activez la connexion automatique, pour que l’agent se reconnecte après un redémarrage. Donnez ensuite le label macos au nœud et envoyez-y vos étapes iOS.

Ce qu’il vous faut d’abord

  • Un contrôleur Jenkins que le Mac peut joindre en HTTPS.
  • Un Mac Apple silicon avec accès administrateur, Homebrew et Xcode.
  • La bonne version de Java. La politique de support Java de Jenkins, lue en octobre 2026, indique que les LTS 2.555.1 et suivantes demandent Java 21 ou 25. Cette règle vaut aussi pour les agents, pas seulement pour le contrôleur.
  • Une session de bureau sur le Mac pour les dernières étapes, par partage d’écran.

1. Installez Java sur le Mac

brew install openjdk@21
/opt/homebrew/opt/openjdk@21/bin/java -version

Homebrew installe ce JDK en keg-only, donc il n’est pas dans votre PATH. Utilisez partout le chemin complet ci-dessus. Cela garde aussi l’agent sur Java 21 quand un JDK plus récent arrive plus tard.

2. Créez le nœud sur le contrôleur

  • Ouvrez Manage Jenkins, puis Nodes, puis New Node. Choisissez Permanent Agent.
  • Number of executors : 1. La documentation de Jenkins elle-même présente un executor par nœud comme le réglage le plus sûr. Les builds Xcode utilisent déjà tous les cœurs.
  • Remote root directory : /Users/YOUR-USER/jenkins.
  • Labels : macos xcode.
  • Usage : only build jobs with label expressions matching this node. Cela tient les jobs Linux à l’écart de votre Mac.
  • Launch method : Launch agent by connecting it to the controller.

Enregistrez. La page du nœud affiche maintenant la commande de lancement, le nom de l’agent et un long secret hexadécimal. Le secret est lié au nom de l’agent. S’il fuit, Jenkins recommande de ne pas réutiliser ce nom.

3. Téléchargez agent.jar et testez à la main

Jenkins fournit le bon agent.jar pour votre contrôleur à l’adresse /jnlpJars/agent.jar. Stockez le secret dans un fichier et passez-le avec @, pour qu’il n’apparaisse jamais dans la liste des processus.

mkdir -p ~/jenkins && cd ~/jenkins
curl -sO https://jenkins.example.com/jnlpJars/agent.jar
echo 'PASTE-THE-SECRET' > secret-file
chmod 600 secret-file
/opt/homebrew/opt/openjdk@21/bin/java -jar agent.jar \
  -url https://jenkins.example.com/ \
  -name mac-mini-1 \
  -secret @secret-file \
  -workDir "$HOME/jenkins" \
  -webSocket

La page du nœud doit passer à connecté. Appuyez sur Contrôle C pour l’arrêter. Avec -webSocket, l’agent ouvre une seule connexion HTTPS. Sans cette option, l’agent a aussi besoin du port TCP entrant séparé du contrôleur.

4. Lancez l’agent depuis un LaunchAgent

launchd démarre l’agent à l’ouverture de session et le relance s’il s’arrête. Lancez ceci depuis le Terminal sur le bureau du Mac. Le heredoc remplit le chemin de votre dossier personnel.

mkdir -p ~/Library/LaunchAgents
cat > ~/Library/LaunchAgents/local.jenkins-agent.plist <<EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>Label</key><string>local.jenkins-agent</string>
  <key>ProgramArguments</key>
  <array>
    <string>/opt/homebrew/opt/openjdk@21/bin/java</string>
    <string>-jar</string><string>$HOME/jenkins/agent.jar</string>
    <string>-url</string><string>https://jenkins.example.com/</string>
    <string>-name</string><string>mac-mini-1</string>
    <string>-secret</string><string>@$HOME/jenkins/secret-file</string>
    <string>-workDir</string><string>$HOME/jenkins</string>
    <string>-webSocket</string>
  </array>
  <key>EnvironmentVariables</key>
  <dict>
    <key>PATH</key><string>/opt/homebrew/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
    <key>LANG</key><string>en_US.UTF-8</string>
  </dict>
  <key>RunAtLoad</key><true/>
  <key>KeepAlive</key><true/>
  <key>ThrottleInterval</key><integer>30</integer>
  <key>StandardOutPath</key><string>$HOME/jenkins/agent.log</string>
  <key>StandardErrorPath</key><string>$HOME/jenkins/agent.log</string>
</dict>
</plist>
EOF
plutil -lint ~/Library/LaunchAgents/local.jenkins-agent.plist
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/local.jenkins-agent.plist
launchctl print gui/$(id -u)/local.jenkins-agent | head -20

Nous avons vérifié ce plist avec plutil -lint. Pourquoi un LaunchAgent et pas un LaunchDaemon ? Un daemon tourne hors de toute session. Or les simulateurs et le trousseau de session vivent dans une session. GitLab et Buildkite donnent la même raison pour leurs agents Mac.

Gardez le plist dans ~/Library/LaunchAgents. Sur macOS 27, nous avons vu un plist placé dans /Library/LaunchAgents casser la connexion automatique à chaque démarrage. Les détails sont dans la connexion automatique sur un Mac headless.

5. Faites-le survivre à un redémarrage

Activez la connexion automatique pour le compte de l’agent dans Réglages Système, sous Utilisateurs et groupes. Empêchez le Mac de se mettre en veille. Puis redémarrez et surveillez la page du nœud.

sudo pmset -a sleep 0
sudo shutdown -r now
# then, after it is back:
tail -n 20 ~/jenkins/agent.log

6. Un Jenkinsfile avec une étape iOS

pipeline {
    agent { label 'macos' }
    options { timeout(time: 30, unit: 'MINUTES') }
    stages {
        stage('Test') {
            steps {
                sh 'xcodebuild -version'
                sh 'rm -rf build/TestResults.xcresult'
                sh "xcodebuild test -project MyApp.xcodeproj -scheme MyApp -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.5' -resultBundlePath build/TestResults.xcresult"
            }
        }
    }
    post {
        always {
            sh 'ditto -c -k --keepParent build/TestResults.xcresult build/TestResults.xcresult.zip || true'
            archiveArtifacts artifacts: 'build/TestResults.xcresult.zip', fingerprint: true
        }
    }
}

Un result bundle est un dossier, donc nous le compressons avec ditto avant de l’archiver. Ouvrez le zip sur n’importe quel Mac et double-cliquez sur le bundle pour le voir dans Xcode.

Deux versions de Xcode sur un agent ? Choisissez-en une par pipeline avec DEVELOPER_DIR. La page de manuel de xcode-select d’Apple indique que cette variable remplace le choix global sans le modifier. Définissez-la dans un bloc environment, pour ne pas toucher aux autres jobs du Mac :

environment {
    DEVELOPER_DIR = '/Applications/Xcode.app/Contents/Developer'
}

Placez ce bloc dans pipeline, à côté de agent. Changer le réglage global avec sudo xcode-select -s marche aussi, mais cela change d’un coup tous les jobs de la machine.

Erreurs courantes et solutions

  • L’agent se connecte, puis disparaît après un redémarrage. Personne n’a ouvert de session, donc le LaunchAgent n’a jamais démarré. Vérifiez la connexion automatique.
  • launchctl bootstrap échoue par SSH avec une erreur de domaine. Lancez-le depuis le Terminal sur le bureau. GitLab et Buildkite documentent la même erreur pour leurs LaunchAgents.
  • L’agent refuse de démarrer et parle de Java. Jenkins vérifie la version de Java au lancement. Alignez-vous sur la version de la politique de support.
  • xcodebuild: error: Existing file at -resultBundlePath. L’espace de travail persiste entre les builds. Supprimez d’abord l’ancien bundle, comme dans le Jenkinsfile.

Pourquoi un Mac dédié aide

Un agent permanent garde son espace de travail et le DerivedData de Xcode entre les builds. Les builds incrémentaux en sont le bénéfice. Dans notre benchmark, nous avons utilisé l’app iOS Wikipedia sur Xcode 26.6, médiane de 3 essais. Un petit changement a été recompilé en 27 secondes sur un M6 déjà chaud. Un runner macos-26 neuf hébergé par GitHub a mis 269 secondes pour le même job.

Quand vous n’en avez pas besoin

Si vous n’utilisez pas déjà Jenkins, ne commencez pas pour une seule app iOS. Un service hébergé avec des runners Mac demande moins d’entretien. Au tarif GitHub de $0.062 la minute macOS (vérifié en septembre 2026), un Mac à prix fixe de $139 est rentabilisé après environ 2,242 minutes par mois. En dessous, la facturation à la minute coûte moins cher. MacRun ne convient pas non plus si votre contrôleur n’accepte que des agents venant d’une IP fixe. Nous n’avons pas d’IP fixe.

Notre page des autres systèmes de CI résume Jenkins sur un Mac MacRun. Le guide du pipeline CI/CD iOS explique quoi mettre dans les étapes.

Questions fréquentes

Un agent Jenkins sur Mac doit-il utiliser SSH ou un lancement entrant ?

+

Le lancement entrant fonctionne quand le Mac peut joindre le contrôleur, mais pas l’inverse. Avec -webSocket, il n’a besoin que de HTTPS vers le contrôleur.

Quelle version de Java faut-il à un agent Jenkins sur macOS ?

+

La même famille que celle du contrôleur. La politique de support de Jenkins, lue en octobre 2026, exige Java 21 ou 25 à partir de la LTS 2.555.1.

Combien d’executors faut-il à un agent Mac ?

+

Commencez avec un seul. Jenkins présente un executor par nœud comme le réglage le plus sûr, et un build Xcode utilise déjà tous les cœurs.

Pourquoi mon agent Jenkins sur Mac ne se reconnecte-t-il pas après un redémarrage ?

+

Un LaunchAgent ne tourne qu’une fois que son utilisateur a ouvert sa session. Activez la connexion automatique pour ce compte et gardez le plist dans ~/Library/LaunchAgents.

Guides associés