Pagine

2013-12-15

Test della ricevuta e Mavericks

È cosa normale che durante lo sviluppo di una nuova versione si facciano test intermedi sulla ricevuta; dato che la versione deve essere la stessa definita in iTunesConnect, mi succede sempre di fare questi primi test utilizzando la versione presente in quel momento sullo Store (una app non può restare un tempo indefinito nello stato Waiting for upload e all'inizio non si ha ancora una stima precisa di quando si finirà lo sviluppo).
Per cui, si effettua il solito procedimento per scaricare la ricevuta dall'AppStore per l'utente di test, fino ad arrivare a quando siamo molto vicini al rilascio della versione finale.

In quel momento, si entra in iTunesConnect, si crea la nuova versione e la si porta nello stato di attesa dei binary. Poi si modifica la versione in Xcode, si esporta l'eseguibile e si esce dall'utenza normale dell'AppStore.

Bene. Prima dell'avvento di Mavericks, il procedimento andava a buon fine senza problemi: all'avvio dell'app c'era un attimo di attesa, poi compariva la richiesta di utente/password; una volta forniti, la ricevuta era scaricata, l'app veniva riavviata, il check della receipt effettuato e, a scanso di bachi inattesi, alla fine si otteneva all'interno del bundle l'attesa cartella _MASReceipt.
La stessa cosa dovrebbe succedere con Mavericks.

Invece, dopo aver cambiato la versione in Xcode e aggiunta su iTunesConnect, il lancio dell'app esportata terminava con un bel dialogo con cui il Finder mi avvisava che l'app era danneggiata e che non poteva essere lanciata (se non nel cestino...).
Dato che la procedura seguita era la stessa, l'unico modo era di andare in debug e osservare cosa accidenti stava succedendo. Risposta: la versione dell'app era diversa da quella registrata nella receipt! Cioè, la ricevuta riguardava la versione vecchia, mentre l'Info.plist conteneva la nuova!
Ma in iTunesConnect la versione era proprio la nuova!

Il caso ha voluto che il giorno successivo prevedessi di non usare il Mac: invece del solito Stop ho dato il comando Spegni. Quando, due giorni dopo, l'ho riacceso, il problema era scomparso: la ricevuta veniva scaricata e la versione era quella giusta!

Dopo qualche giro sulla rete e dopo aver fatto una prova simile su un'altra app, sono arrivato alla soluzione: senza dire nulla (o per lo meno, io non ho trovato indicazioni), Apple ha aggiunto in Mavericks la possibilità di tenere in cache una ricevuta e di riutilizzarla se la stessa applicazione la richiede (fino a quando questa cache è disponibile). Di solito questa è una cosa buona, poiché si evita di fare una nuova richiesta, una nuova attesa e un nuovo download. In questo caso invece crea questo problema.

La soluzione (oltre al riavvio, ma è esagerato!) è di killare lo StoreAgent:

$ killall -KILL storeagent

e dopo mettere l'app nel cestino e svuotarlo. A questo punto, esportiamo una nuova copia dell'app da Xcode, lanciamola, lo StoreAgent viene rilanciato da zero, forniamo utente test/password e... magicamente tutto torna a posto!

2013-12-07

Firmare app e framework

Una delle novità di Mavericks è che non sia più possibile firmare un'applicazione con il proprio certificato sviluppatore se all'interno si trova un framework non firmato.
Ho scoperto questa "bella" novità quando ho deciso di re-introdurre l'utilizzo dell'utilissimo Sparkle per l'aggiornamento automatico delle applicazioni, per rilasci fuori Mac App Store. Giusto per completezza, la stessa situazione si trova comunque ogni volta che utilizziamo un framework esterno o mini-applicazioni per estendere le funzionalità della nostra app.
A pensarci, è anche giusto che sia stata introdotta questa sicurezza aggiuntiva: in effetti un framework potrebbe effettuare azioni potenzialmente dannose e nascondersi dentro alla nostra app; tuttavia, avrei preferito un bel banner che avvisasse di questo problema, prima di sbatterci il naso per un po' di tempo...

Il sintomo del problema compare quando tentiamo di compilare con il nostro bravo framework importato; il passaggio della firma fornisce un errore del genere:

codesign build/release.../myApp.app: code object is not signed at all
in subcomponent build/release..../Sparkle.framework 
codesign failed with exit code 1

La parte "in subcomponent" ci indica anche quale framework (o app) è responsabile della mancata firma.
Il suggerimento di alcuni siti di inserire il comando --deep non risolve: purtroppo questa opzione provoca sì la firma di tutti i bundle ai vari livelli, ma applicando le stesse proprietà richieste dall'app principale. Il risultato è che il procedimento di firma va a buon fine, ma quando un utente lancia la nostra applicazione, il Finder gli mostra un bel messaggio che lo invita a cestinarla, perché danneggiata. Il problema viene mostrato anche tramite il comando da Terminale spctl:

spctl --verbose=4 --assess myApp.app 
myApp.app: a sealed resource is missing or invalid

La soluzione, tutto sommato, è abbastanza semplice (una volta che la si conosce): basta infatti firmare, con lo stesso certificato dell'App, anche il framework, dopo che questo è stato copiato durante i passaggi della compilazione.
Per far questo, si seleziona il progetto e si va nella pagina delle Build Phases, dove avremo già aggiunto in precedenza una Copy Phase, per fare in modo che il framework sia copiato nel bundle dell'applicazione (precisamente, nella cartella Framework, che verrà quindi creata se non esiste). Dal menu Editor, scegliere Add Build Phase  Add Run Script Build Phase; lasciando la shell di default, scriviamo nel riquadro dedicato il seguente script (riferendosi al framework Sparkle):

LOCATION="${BUILT_PRODUCTS_DIR}"/"${FRAMEWORKS_FOLDER_PATH}" 
IDENTITY="Mac Developer: abcdef ghijk" 
codesign --verbose --force --sign "$IDENTITY" "$LOCATION/Sparkle.framework/Versions/A"

dove nella variabile IDENTITY andiamo ad inserire il nome del certificato da utilizzare (che dovrà essere lo stesso utilizzato per l'applicazione, altrimenti l'app verrà vista come danneggiata).
A questo punto il problema è risolto! Da notare che se avessimo altri framework o applicazioni da inserire nel bundle, avremo script aggiuntivi, uno per ciascuno di essi, da eseguire dopo la copia nel percorso adatto.

Per comodità, aggiungo a questo post anche i comandi shell per il controllo della firma.
Per verificare che tutto è a posto:

codesign --verify --verbose=4 myApp.app

Per essere sicuri che Gatekeeper accetti la nostra app (fuori dal Mac App Store):

spctl --verbose=4 --assess --type execute myApp.app

Per esaminare i requirement di una app:

codesign --display --requirements - myApp.app