Guida

Come aggiungere un Mac come agente Jenkins per le build iOS

Per aggiungere un Mac a Jenkins, crea un nodo permanente che si collega al controller. Sul Mac, installa Java 21 ed esegui agent.jar con l’opzione -webSocket. Metti quel comando in un LaunchAgent e attiva il login automatico, così l’agente si ricollega dopo un riavvio. Poi assegna al nodo l’etichetta macos e manda lì i tuoi stage iOS.

Cosa ti serve prima

  • Un controller Jenkins che il Mac può raggiungere via HTTPS.
  • Un Mac Apple silicon con accesso amministratore, Homebrew e Xcode.
  • La versione giusta di Java. La policy di supporto Java di Jenkins, letta a ottobre 2026, dice che la LTS 2.555.1 e successive richiedono Java 21 o 25. La regola vale anche per gli agenti, non solo per il controller.
  • Una sessione desktop sul Mac per gli ultimi passi, con la condivisione schermo.

1. Installa Java sul Mac

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

Homebrew installa questo JDK come keg-only, quindi non è nel tuo PATH. Usa ovunque il percorso completo qui sopra. Così l’agente resta anche su Java 21 quando in seguito arriva un JDK più nuovo.

2. Crea il nodo sul controller

  • Apri Manage Jenkins, poi Nodes, poi New Node. Scegli Permanent Agent.
  • Number of executors: 1. La documentazione di Jenkins stessa definisce un executor per nodo l’impostazione più sicura. Le build Xcode usano già tutti i core.
  • Remote root directory: /Users/YOUR-USER/jenkins.
  • Labels: macos xcode.
  • Usage: only build jobs with label expressions matching this node. Così i job Linux restano fuori dal tuo Mac.
  • Launch method: Launch agent by connecting it to the controller.

Salva. Ora la pagina del nodo mostra il comando da eseguire, il nome dell’agente e un lungo secret esadecimale. Il secret è legato al nome dell’agente. Se viene divulgato, Jenkins dice di non riusare quel nome.

3. Scarica agent.jar e prova a mano

Jenkins fornisce l’agent.jar giusto per il tuo controller su /jnlpJars/agent.jar. Salva il secret in un file e passalo con @, così non compare mai in un elenco dei processi.

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 pagina del nodo dovrebbe passare a connesso. Premi Control C per fermarlo. Con -webSocket, l’agente apre una sola connessione HTTPS. Senza, l’agente ha bisogno anche della porta TCP inbound separata del controller.

4. Avvia l’agente da un LaunchAgent

launchd avvia l’agente al login e lo riavvia se termina. Esegui questo dal Terminale sul desktop del Mac. L’heredoc inserisce il percorso della tua cartella home.

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

Abbiamo verificato questo plist con plutil -lint. Perché un LaunchAgent e non un LaunchDaemon? Un daemon gira fuori da qualsiasi sessione utente. I simulatori e il portachiavi di login vivono dentro una sessione. GitLab e Buildkite danno lo stesso motivo per i loro agenti Mac.

Tieni il plist in ~/Library/LaunchAgents. Su macOS 27 abbiamo visto un plist in /Library/LaunchAgents bloccare il login automatico a ogni avvio. I dettagli sono in login automatico su un Mac headless.

5. Fallo sopravvivere a un riavvio

Attiva il login automatico per l’account dell’agente in Impostazioni di Sistema, in Utenti e gruppi. Impedisci al Mac di andare in stop. Poi riavvia e guarda la pagina del nodo.

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

6. Un Jenkinsfile con uno stage 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 è una cartella, quindi lo comprimiamo con ditto prima di archiviarlo. Apri lo zip su qualsiasi Mac e fai doppio clic sul bundle per vederlo in Xcode.

Due versioni di Xcode su un solo agente? Scegline una per pipeline con DEVELOPER_DIR. La pagina man di xcode-select di Apple dice che sovrascrive la scelta di sistema senza cambiarla. Impostala in un blocco environment, così gli altri job sul Mac non ne risentono:

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

Metti quel blocco dentro pipeline, accanto a agent. Anche cambiare l’impostazione globale con sudo xcode-select -s funziona, ma cambia tutti i job sulla macchina in un colpo solo.

Errori comuni e soluzioni

  • L’agente si collega, poi sparisce dopo un riavvio. Nessuno ha fatto il login, quindi il LaunchAgent non è mai partito. Controlla il login automatico.
  • launchctl bootstrap fallisce via SSH con un errore di dominio. Eseguilo dal Terminale sul desktop. GitLab e Buildkite documentano lo stesso errore per i loro LaunchAgent.
  • L’agente non parte e cita Java. Jenkins controlla la versione di Java all’avvio. Usa la versione indicata nella policy di supporto.
  • xcodebuild: error: Existing file at -resultBundlePath. Il workspace resta tra una build e l’altra. Cancella prima il vecchio bundle, come nel Jenkinsfile.

Perché un Mac dedicato aiuta

Un agente permanente conserva il suo workspace e la DerivedData di Xcode tra una build e l’altra. Il vantaggio sono le build incrementali. Nel nostro benchmark abbiamo usato l’app iOS di Wikipedia su Xcode 26.6, mediana di 3 esecuzioni. Dopo una piccola modifica, la build ha richiesto 27 secondi su un M6 caldo. Un runner macos-26 nuovo ospitato da GitHub ha richiesto 269 secondi per lo stesso job.

Quando non ti serve

Se non usi già Jenkins, non iniziare per una sola app iOS. Un servizio in hosting con runner Mac richiede meno manutenzione. Con la tariffa di GitHub di $0.062 per minuto macOS (verificata a settembre 2026), un Mac a prezzo fisso di $139 si ripaga dopo circa 2,242 minuti al mese. Sotto quella soglia, il consumo costa meno. MacRun non fa per te nemmeno se il tuo controller accetta agenti solo da un IP fisso. Non abbiamo IP statici.

La nostra pagina sugli altri sistemi di CI spiega in breve Jenkins su un Mac MacRun. La guida alla pipeline CI/CD iOS spiega cosa mettere negli stage.

Domande frequenti

Un agente Jenkins su Mac deve usare SSH o l’avvio inbound?

+

L’avvio inbound funziona quando il Mac può raggiungere il controller ma non il contrario. Con -webSocket serve solo HTTPS verso il controller.

Quale versione di Java serve a un agente Jenkins su macOS?

+

La stessa famiglia che serve al controller. La policy di supporto di Jenkins, letta a ottobre 2026, richiede Java 21 o 25 dalla LTS 2.555.1 in poi.

Quanti executor deve avere un agente Mac?

+

Inizia con uno. Jenkins definisce un executor per nodo l’impostazione più sicura, e una build Xcode usa già tutti i core.

Perché il mio agente Jenkins su Mac non si ricollega dopo un riavvio?

+

Un LaunchAgent parte solo quando il suo utente fa il login. Attiva il login automatico per quell’account e tieni il plist in ~/Library/LaunchAgents.

Guide correlate