Sisteme CI pentru dezvoltarea iOS: transformarea Intel la ARM

Publicat: 2024-02-14

În peisajul în continuă evoluție al tehnologiei, companiile trebuie să se adapteze vântului schimbării pentru a rămâne relevante și competitive. O astfel de transformare care a luat cu asalt lumea tehnologiei este tranziția de la arhitectura Intel x86_64 la arhitectura iOS ARM, exemplificată de cipul Apple M1 inovator al Apple. În acest context, sistemele CI pentru iOS au devenit o considerație crucială pentru companiile care navighează în această schimbare, asigurându-se că procesele de dezvoltare și testare a software-ului rămân eficiente și la zi cu cele mai recente standarde tehnologice.

Apple și-a anunțat cipurile M1 în urmă cu aproape trei ani și, de atunci, a fost clar că compania va adopta arhitectura ARM și, în cele din urmă, va renunța la suportul pentru software-ul bazat pe Intel. Pentru a menține compatibilitatea între arhitecturi, Apple a introdus o nouă versiune a Rosetta, cadrul său de traducere binară proprietară, care s-a dovedit a fi fiabilă în trecut în timpul transformării semnificative a arhitecturii de la PowerPC la Intel din 2006. Transformarea este încă în desfășurare și am văzut Xcode își pierde suportul Rosetta în versiunea 14.3.

La Miquido, am recunoscut nevoia de a migra de la Intel la ARM acum câțiva ani. Am început pregătirile la jumătatea anului 2021. Fiind o casă de software cu mai mulți clienți, aplicații și proiecte în desfășurare concomitent, ne-am confruntat cu câteva provocări pe care a trebuit să le depășim. Acest articol poate fi ghidul tău în cazul în care compania ta se confruntă cu situații similare. Situațiile și soluțiile descrise sunt descrise dintr-o perspectivă de dezvoltare iOS – dar este posibil să găsiți perspective potrivite și pentru alte tehnologii. O componentă critică a strategiei noastre de tranziție a implicat să ne asigurăm că sistemul nostru CI pentru iOS a fost pe deplin optimizat pentru noua arhitectură, subliniind importanța Sisteme CI pentru iOS în menținerea fluxurilor de lucru eficiente și a rezultatelor de înaltă calitate în mijlocul unei schimbări atât de semnificative.

Problema: Migrarea arhitecturii Intel la iOS ARM

Apple și-a lansat cipul M1 în 2020

Procesul de migrare a fost împărțit în două ramuri principale.

1. Înlocuirea computerelor existente ale dezvoltatorilor bazate pe Intel cu noi Macbook-uri M1

Acest proces trebuia să fie relativ simplu. Am stabilit o politică pentru a înlocui treptat toate Macbook-urile Intel ale dezvoltatorului pe parcursul a doi ani. În prezent, 95% dintre angajații noștri folosesc Macbook-uri bazate pe ARM.

Cu toate acestea, ne-am confruntat cu unele provocări neașteptate în timpul acestui proces. La mijlocul anului 2021, lipsa de Mac-uri M1 a încetinit procesul nostru de înlocuire. Până la sfârșitul anului 2021, am reușit să înlocuim doar o mână de Macbook-uri din cele aproape 200 de cele care așteptau. Am estimat că ar dura aproximativ doi ani pentru a înlocui complet toate Mac-urile Intel ale companiei cu Macbook-uri M1, inclusiv inginerii non-iOS.

Din fericire, Apple a lansat noile lor cipuri M1 Pro și M2. Drept urmare, ne-am mutat atenția de la înlocuirea Intel-urilor cu Mac-uri M1 la înlocuirea lor cu cipuri M1 Pro și M2.

Software-ul care nu este pregătit pentru comutare a cauzat frustrarea dezvoltatorilor

Primii ingineri care au primit noile Macbook-uri M1 au avut o perioadă dificilă, deoarece majoritatea software-ului nu era pregătit să treacă la noua arhitectură iOS ARM a Apple. Instrumentele terțe precum Rubygems și Cocoapods, care sunt instrumente de gestionare a dependenței care se bazează pe multe alte Rubygems, au fost cele mai afectate. Unele dintre aceste instrumente nu au fost compilate pentru arhitectura iOS ARM atunci, așa că majoritatea software-ului a trebuit să fie rulat folosind Rosetta, provocând probleme de performanță și frustrare.

Cu toate acestea, creatorii de software au lucrat pentru a rezolva majoritatea acestor probleme pe măsură ce au apărut. Momentul de descoperire a venit odată cu lansarea Xcode 14.3, care nu mai avea suport Rosetta. Acesta a fost un semnal clar pentru toți dezvoltatorii de software că Apple facea eforturi pentru o migrare a arhitecturii Intel la iOS ARM. Acest lucru i-a forțat pe majoritatea dezvoltatorilor de software terți care se bazaseră anterior pe Rosetta să-și migreze software-ul la ARM. În zilele noastre, 99% din software-ul terță parte utilizat zilnic la Miquido funcționează fără Rosetta.

2. Înlocuirea sistemului CI de la Miquido pentru iOS

Înlocuirea sistemului iOS de integrare continuă la Miquido s-a dovedit a fi o sarcină mai complicată decât simpla schimbare a mașinilor. Mai întâi, vă rugăm să aruncați o privire la infrastructura noastră de atunci:

Arhitectura CI la Miquido. Sistem CI pentru transformarea iOS.

Aveam o instanță cloud Gitlab și 9 Mac Mini-uri bazate pe Intel conectate la ea. Aceste mașini serveau ca agenți de muncă, iar Gitlab era responsabil de orchestrare. Ori de câte ori un job CI era pus în coadă, Gitlab îl atribuia primului rulant disponibil care îndeplinea cerințele proiectului specificate în fișierul gitlab-ci.yml. Gitlab ar crea un script de job care să conțină toate comenzile de construire, variabilele, căile etc. Acest script a fost apoi mutat pe runner și executat pe acea mașină.

Deși această configurație poate părea robustă, ne-am confruntat cu probleme cu virtualizarea din cauza suportului slab al procesoarelor Intel. Drept urmare, am decis să nu folosim virtualizări precum Docker și să executăm lucrări pe mașinile fizice în sine. Am încercat să creăm o soluție eficientă și fiabilă bazată pe Docker, dar limitările de virtualizare, cum ar fi lipsa accelerării GPU, au dus la executarea lucrărilor de două ori mai mult decât pe mașinile fizice. Acest lucru a dus la mai multe cheltuieli generale și la umplerea rapidă a cozilor.

Datorită SLA macOS, am putut configura doar două VM simultan. Prin urmare, am decis să extindem grupul de alergători fizici și să îi setăm să execute joburi Gitlab direct pe sistemul lor de operare. Cu toate acestea, această abordare a avut și câteva dezavantaje.

Provocări în procesul de construire și managementul alergătorului

  1. Nicio izolare a versiunilor în afara sandbox-ului directorului de compilare.

Runnerul execută fiecare build pe o mașină fizică, ceea ce înseamnă că build-urile nu sunt izolate din sandbox-ul directorului de build. Acest lucru are avantajele și dezavantajele sale. Pe de o parte, putem folosi cache-urile de sistem pentru a accelera construirea, deoarece majoritatea proiectelor folosesc același set de dependențe de la terți.

Pe de altă parte, memoria cache devine de neîntreținut, deoarece resturile de la un proiect pot afecta orice alt proiect. Acest lucru este deosebit de important pentru cache-urile la nivel de sistem, deoarece aceleași runneri sunt utilizate atât pentru dezvoltarea Flutter, cât și pentru React Native. React Native, în special, necesită multe dependențe stocate în cache prin NPM.

  1. Potențial dezordine cu instrumentele sistemului.

Deși niciuna dintre sarcini nu a fost executată cu privilegii sudo, le era totuși posibil să acceseze unele dintre instrumentele de sistem sau utilizator, cum ar fi Ruby. Acest lucru a reprezentat o potențială amenințare de a distruge unele dintre aceste instrumente, mai ales că macOS folosește Ruby pentru unele dintre software-ul său moștenit, inclusiv unele funcții Xcode vechi. Versiunea de sistem a lui Ruby nu este ceva cu care ați dori să vă încurcați.

Cu toate acestea, introducerea rbenv creează un alt strat de complexitate cu care trebuie tratat. Este important să rețineți că Rubygems sunt instalate pe versiunea Ruby, iar unele dintre aceste pietre necesită versiuni specifice de Ruby. Aproape toate instrumentele terțe pe care le folosim erau dependente de Ruby, Cocoapods și Fastlane fiind actorii principali.

  1. Gestionarea identităților de semnătură.

Gestionarea identităților de semnare multiple de la diferite conturi de dezvoltare a clienților poate fi o provocare atunci când vine vorba de brelocurile de sistem ale alergătorilor. Identitatea de semnare este o piesă de date extrem de sensibilă , deoarece ne permite să co-proiectăm aplicația, făcând-o vulnerabilă la potențiale amenințări.

Pentru a asigura securitatea, identitățile ar trebui să fie sandbox între proiecte și protejate. Cu toate acestea, acest proces poate deveni un coșmar având în vedere complexitatea adăugată introdusă de macOS în implementarea brelocului lor.

  1. Provocări în medii cu mai multe proiecte.

Nu toate proiectele au fost create folosind aceleași instrumente, în special Xcode. Unele proiecte, în special cele aflate în faza de suport, au fost menținute folosind ultima versiune de Xcode cu care a fost dezvoltat proiectul. Aceasta înseamnă că, dacă era necesară vreo lucrare la acele proiecte, CI trebuia să fie capabil să o construiască. Drept urmare, alergătorii au trebuit să accepte mai multe versiuni de Xcode în același timp, ceea ce a redus efectiv numărul de alergători disponibili pentru o anumită sarcină.

5. Este necesar un efort suplimentar.

Orice modificare făcută pe toți alergătorii, cum ar fi instalarea software-ului, trebuie efectuată simultan pe toți alergătorii. Deși aveam un instrument de automatizare pentru aceasta, a necesitat un efort suplimentar pentru a menține scripturile de automatizare.

Soluții de infrastructură personalizate pentru diverse nevoi ale clienților

Miquido este o casă de software care lucrează cu mai mulți clienți cu nevoi diferite. Personalizăm serviciile noastre pentru a răspunde cerințelor specifice fiecărui client. Adesea, găzduim baza de cod și infrastructura necesară pentru întreprinderile mici sau start-up-uri, deoarece acestea ar putea să nu aibă resursele sau cunoștințele necesare pentru a le menține.

Clienții Enterprise au de obicei propria infrastructură pentru a-și găzdui proiectele. Cu toate acestea, unii nu au capacitatea de a face acest lucru sau sunt obligați de reglementările industriei să-și folosească infrastructura. De asemenea, preferă să nu folosească niciun serviciu SaaS terță parte, cum ar fi Xcode Cloud sau Codemagic. În schimb, doresc o soluție care să se potrivească arhitecturii lor existente.

Pentru a găzdui acești clienți, adesea găzduim proiectele pe infrastructura noastră sau setăm aceeași configurație iOS de integrare continuă pe infrastructura lor. Cu toate acestea, avem o grijă deosebită atunci când avem de-a face cu informații și fișiere sensibile, cum ar fi semnarea identităților.

Folosind Fastlane pentru un management eficient al construcției

Aici Fastlane vine ca un instrument util. Este format din diverse module numite acțiuni care ajută la eficientizarea procesului și la separarea acestuia între diferiți clienți. Una dintre aceste acțiuni, numită potrivire, ajută la menținerea identităților de semnare de dezvoltare și producție, precum și la furnizarea profilurilor. De asemenea, funcționează la nivel de sistem de operare pentru a separa acele identități în brelocuri separate pentru timpul de construire și efectuează o curățare după compilare, ceea ce este foarte util deoarece rulăm toate versiunile noastre pe mașini fizice.

Fastlane: instrument de automatizare a dezvoltării
Credite de imagine: Fastlane

Am apelat inițial la Fastlane dintr-un motiv anume, dar am descoperit că are funcții suplimentare care ne-ar putea fi utile.

  1. Încărcare build în Testflight

În trecut, API-ul AppStoreConnect nu era disponibil public pentru dezvoltatori. Aceasta însemna că singura modalitate de a încărca o versiune în Testflight a fost prin Xcode sau prin Fastlane. Fastlane a fost un instrument care, în esență, a eliminat API-ul ASC și l-a transformat într-o acțiune numită pilot . Cu toate acestea, această metodă s-a rupt adesea cu următoarea actualizare Xcode. Dacă un dezvoltator dorea să-și încarce construcția în Testflight folosind linia de comandă, Fastlane era cea mai bună opțiune disponibilă.

  1. Comutare ușoară între versiunile Xcode

Având mai mult de o instanță Xcode pe o singură mașină, a fost necesar să se selecteze ce cod Xcode să folosească pentru compilare. Din păcate, Apple a făcut incomod să comutați între versiunile Xcode - trebuie să utilizați „xcode-select” pentru a face acest lucru, ceea ce necesită, în plus, privilegii sudo. Fastlane acoperă și asta.

  1. Utilități suplimentare pentru dezvoltatori

Fastlane oferă multe alte utilități utile, inclusiv versiunea și capacitatea de a trimite rezultate ale compilației către webhook-uri.

Dezavantajele Fastlane

Adaptarea Fastlane la proiectele noastre a fost solidă și solidă, așa că am mers în această direcție. L-am folosit cu succes de câțiva ani. Cu toate acestea, de-a lungul acestor ani, am identificat câteva probleme:

  1. Fastlane necesită cunoștințe despre Ruby.

Fastlane este un instrument care este scris în Ruby și necesită cunoștințe bune despre Ruby pentru a-l folosi eficient. Când există erori în configurația dvs. Fastlane sau în instrumentul în sine, depanarea lor folosind irb sau pry poate fi destul de dificilă.

  1. Dependență de numeroase pietre prețioase.

Fastlane în sine se bazează pe aproximativ 70 de pietre prețioase. Pentru a atenua riscurile de distrugere a sistemului Ruby, proiectele foloseau pietre prețioase locale. Preluarea tuturor acestor pietre prețioase a creat o mulțime de timp.

  1. Probleme de sistem Ruby și rubygems.

Ca rezultat, toate problemele cu sistemul Ruby și rubygems menționate mai devreme sunt de asemenea aplicabile aici.

  1. Redundanță pentru proiecte Flutter.

Proiectele Flutter au fost, de asemenea, forțate să folosească potrivirea rapidă doar pentru a păstra compatibilitatea cu proiectele iOS și pentru a proteja brelocurile alergătorului. Acest lucru a fost absurd de inutil, deoarece Flutter are propriul sistem de construcție încorporat, iar costul general menționat mai devreme a fost introdus doar pentru a gestiona identitățile de semnare și profilurile de furnizare.

Cele mai multe dintre aceste probleme au fost rezolvate pe parcurs, dar aveam nevoie de o soluție mai robustă și mai fiabilă.

Ideea: adaptarea unor instrumente noi, mai robuste de integrare continuă pentru iOS

Vestea bună este că Apple a câștigat controlul deplin asupra arhitecturii sale de cip și a dezvoltat un nou cadru de virtualizare pentru macOS. Acest cadru permite utilizatorilor să creeze, să configureze și să ruleze mașini virtuale Linux sau macOS care pornesc rapid și se caracterizează printr-o performanță nativă - și chiar mă refer la nativ.

Acest lucru a părut promițător și ar putea fi o piatră de temelie pentru noile noastre instrumente de integrare continuă pentru iOS. Cu toate acestea, a fost doar o bucată de soluție completă. Având un instrument de management VM, aveam nevoie și de ceva care să poată folosi acel cadru în coordonare cu cei care rulează Gitlab.

Având asta, majoritatea problemelor noastre privind performanța slabă a virtualizării ar deveni învechite. De asemenea, ne-ar permite să rezolvăm automat majoritatea problemelor pe care intenționam să le rezolvăm cu Fastlane.

Dezvoltarea unei soluții personalizate pentru managementul identității de semnătură la cerere

Am avut o ultimă problemă de rezolvat – managementul identității semnării. Nu am vrut să folosim Fastlane pentru asta, deoarece ni s-a părut excesiv pentru nevoile noastre. În schimb, căutam o soluție care să fie mai adaptată cerințelor noastre. Nevoile noastre erau simple: procesul de gestionare a identității trebuia făcut la cerere, exclusiv pentru timpul de construire, fără identități preinstalate pe breloc și să fie compatibil cu orice mașină pe care ar rula.

Problema distribuției și lipsa API-ului AppstoreConnect stabil au devenit învechite când Apple și-a lansat „toolul” care a permis comunicarea între utilizatori și ASC.

Așa că am avut o idee și a trebuit să găsim o modalitate de a conecta aceste trei aspecte:

  1. Găsirea unei modalități de a utiliza cadrul de virtualizare Apple.
  2. Fă-l să funcționeze cu alergătorii Gitlab.
  3. Găsirea unei soluții pentru gestionarea identității semnăturii în mai multe proiecte și participanți.

Soluția: o privire asupra abordării noastre (instrumente incluse)

Am început să căutăm soluții pentru a rezolva toate problemele menționate mai devreme.

  1. Folosind cadrul de virtualizare Apple.

Pentru primul obstacol, am găsit destul de repede o soluție: am dat peste instrumentul pentru tartă de la Cirrus Labs. Din prima clipă, am știut că aceasta va fi alegerea noastră.

Cele mai semnificative avantaje ale utilizării instrumentului de tartă oferit de Cirrus Lab sunt:

  • Posibilitatea de a crea vms din imagini brute .ipsw.
  • Posibilitatea de a crea vms folosind șabloane pre-ambalate (cu unele instrumente utilitare instalate, cum ar fi brew sau Xcode), disponibilă pe pagina GitHub a Cirrus Labs.
  • Instrumentul Tart folosește packer pentru suport dinamic pentru construirea imaginii.
  • Instrumentul Tart acceptă atât imagini Linux, cât și MacOS.
  • Instrumentul utilizează o caracteristică remarcabilă a sistemului de fișiere APFS care permite duplicarea fișierelor fără a rezerva spațiu pe disc pentru ele. În acest fel, nu trebuie să alocați spațiu pe disc de 3 ori dimensiunea originală a imaginii. Aveți nevoie doar de suficient spațiu pe disc pentru imaginea originală, în timp ce clona ocupă doar spațiul care este o diferență între ea și imaginea originală. Acest lucru este incredibil de util, mai ales că imaginile macOS tind să fie destul de mari.

De exemplu, o imagine operațională macOS Ventura cu Xcode și alte utilitare instalate necesită un spațiu de minim de 60 GB. În circumstanțe normale, o imagine și două dintre clonele sale ar ocupa până la 180 GB de spațiu pe disc, ceea ce este o cantitate semnificativă. Și acesta este doar începutul, deoarece poate doriți să aveți mai multe imagini originale sau să instalați mai multe versiuni Xcode pe o singură VM, ceea ce ar crește și mai mult dimensiunea.

  • Instrumentul permite gestionarea adreselor IP pentru VM-urile originale și clonate, permițând accesul SSH la VM-urile.
  • Abilitatea de a monta directoare între mașina gazdă și VM.
  • Instrumentul este ușor de utilizat și are un CLI foarte simplu.

Nu există aproape nimic din care să îi lipsească acestui instrument în ceea ce privește utilizarea lui pentru managementul VM. Aproape nimic, cu excepția unui singur lucru: deși promițător, plugin-ul de ambalare pentru a construi imagini din mers a consumat excesiv de timp, așa că am decis să nu-l folosim.

Am încercat tarta și a funcționat fantastic. Performanța sa a fost nativă, iar gestionarea a fost ușoară.

După ce am integrat cu succes tarta cu rezultate impresionante, ne-am concentrat apoi pe abordarea altor provocări.

  1. Găsirea unei modalități de a combina tarta cu alergătorii Gitlab.

După rezolvarea primei probleme, ne-am confruntat cu întrebarea cum să combinăm tarta cu rularele Gitlab.

Să începem prin a descrie ce fac de fapt alergătorii Gitlab:

Diagrama simplificată a delegării locurilor de muncă Gitlab. Sistem CI pentru iOS

Trebuia să includem un puzzle suplimentar în diagramă, care presupunea alocarea sarcinilor de la gazda runner la VM. Jobul GitLab este un script shell care conține variabile cruciale, intrări PATH și comenzi.

Obiectivul nostru a fost să transferăm acest script pe VM și să-l rulăm.

Cu toate acestea, această sarcină s-a dovedit a fi mai provocatoare decât am crezut inițial.

Alergatorul

Executorii standard Gitlab runner, cum ar fi Docker sau SSH, sunt simplu de configurat și necesită puțină sau deloc configurare. Cu toate acestea, aveam nevoie de un control mai mare asupra configurației, ceea ce ne-a determinat să explorăm executanții personalizați furnizați de GitLab.

Executorii personalizați sunt o opțiune excelentă pentru configurațiile non-standard, deoarece fiecare pas de rulare (pregătire, execuție, curățare) este descris sub forma unui script shell. Singurul lucru care lipsea a fost un instrument de linie de comandă care ar putea îndeplini sarcinile de care aveam nevoie și să fie executat în scripturile de configurare ale runnerului.

În prezent, există câteva instrumente disponibile care fac exact asta - de exemplu, executatorul de tartă CirrusLabs Gitlab. Acest instrument este exact ceea ce căutam la momentul respectiv. Cu toate acestea, nu a existat încă și, după efectuarea cercetărilor, nu am găsit niciun instrument care să ne ajute să ne îndeplinim sarcina.

Scrierea propriei soluții

Deoarece nu am putut găsi o soluție perfectă, am scris-o singuri. Suntem ingineri, până la urmă! Ideea părea solidă și aveam toate instrumentele necesare, așa că am trecut la dezvoltare.

Am ales să folosim Swift și câteva biblioteci open-source furnizate de Apple: Swift Argument Parser pentru a gestiona execuția liniei de comandă și Swift NIO pentru a gestiona conexiunea SSH cu mașinile virtuale. Am început dezvoltarea și, în câteva zile, am obținut primul prototip funcțional al unui instrument care a evoluat în cele din urmă în MQVMRunner.

Infrastructura iOS CI: MQVMRunner

La un nivel înalt, instrumentul funcționează după cum urmează:

  1. (Pregătiți pasul)
    1. Citiți variabilele furnizate în gitlab-ci.yml (numele imaginii și variabilele suplimentare).
    2. Alegeți baza VM solicitată
    3. Clonează baza VM solicitată.
    4. Configurați un director montat încrucișat și copiați scriptul de lucru Gitlab în, setând permisiunile necesare pentru acesta.
    5. Rulați clona și verificați conexiunea SSH.
    6. Configurați orice dependențe necesare (cum ar fi versiunea Xcode), dacă este necesar.
  2. (Executa pasul)
    1. Rulați jobul Gitlab executând un script dintr-un director montat încrucișat pe o clonă de VM pregătită prin SSH.
  3. (pas de curățare)
    1. Ștergeți imaginea clonată.

Provocări în dezvoltare

În timpul dezvoltării, am întâmpinat mai multe probleme, care au făcut ca acesta să nu meargă atât de bine pe cât ne-am fi dorit.

  1. gestionarea adresei IP.

Gestionarea adreselor IP este o sarcină crucială care trebuie gestionată cu grijă. În prototip, manipularea SSH a fost implementată folosind comenzi shell SSH directe și codificate. Cu toate acestea, în cazul shell-urilor non-interactive, se recomandă autentificarea cheii. În plus, este recomandabil să adăugați gazda la fișierul known_hosts pentru a evita întreruperi. Cu toate acestea, datorită gestionării dinamice a adreselor IP ale mașinilor virtuale, există posibilitatea de a dubla intrarea pentru un anumit IP, ceea ce duce la erori. Prin urmare, trebuie să atribuim cunoscutele_gazde în mod dinamic pentru o anumită sarcină pentru a preveni astfel de probleme.

  1. Soluție Pure Swift.

Având în vedere asta și faptul că comenzile shell codificate în codul Swift nu sunt chiar elegante, ne-am gândit că ar fi bine să folosim o bibliotecă Swift dedicată și am decis să mergem cu Swift NIO. Am rezolvat unele probleme, dar, în același timp, am introdus câteva noi, cum ar fi – de exemplu – uneori jurnalele plasate pe stdout erau transferate *după ce* canalul SSH a fost terminat din cauza execuției terminate a comenzii – și, așa cum ne bazam pe acea ieșire în lucrările ulterioare, execuția a eșuat aleatoriu.

  1. Selectarea versiunii Xcode.

Deoarece pluginul Packer nu era o opțiune pentru construirea dinamică a imaginilor din cauza consumului de timp, am decis să mergem cu o singură bază de VM cu mai multe versiuni Xcode preinstalate. A trebuit să găsim o modalitate prin care dezvoltatorii să specifice versiunea Xcode de care au nevoie în gitlab-ci.yml - și am venit cu variabile personalizate disponibile pentru a fi utilizate în orice proiect. MQVMRunner va executa apoi `xcode-select` pe un VM clonat pentru a configura versiunea Xcode corespunzătoare.

Și multe, multe altele

Eficientizarea migrației proiectelor și integrarea continuă pentru fluxul de lucru iOS cu Mac Studios

Am instalat-o pe două noi Mac Studios și am început să migrăm proiectele. Am vrut să facem procesul de migrare pentru dezvoltatorii noștri cât mai transparent posibil. Nu am putut face totul perfect, dar în cele din urmă, am ajuns la punctul în care au trebuit să facă doar câteva lucruri în gitlab-ci.yml:

  • Etichetele alergătorilor: să folosești Mac Studios în loc de Intels.
  • Numele imaginii: parametru opțional, introdus pentru compatibilitate viitoare în cazul în care avem nevoie de mai multe VM de bază. Momentan, este întotdeauna implicit la VM unică de bază pe care o avem.
  • Versiunea Xcode: parametru opțional; dacă nu este furnizată, va fi utilizată cea mai nouă versiune disponibilă.

Instrumentul a primit feedback inițial foarte bun, așa că am decis să îl facem open-source. Am adăugat un script de instalare pentru a configura Gitlab Custom Runner și toate acțiunile și variabilele necesare. Folosind instrumentul nostru, vă puteți configura propriul ruler GitLab în câteva minute - singurul lucru de care aveți nevoie este VM-ul de bază și tart pe care vor fi executate joburile.

Integrarea finală continuă pentru structura iOS arată după cum urmează:

Infrastructura finală CI: MQVMRunner

3. Soluție pentru managementul eficient al identității

Ne-am străduit să găsim o soluție eficientă pentru gestionarea identităților de semnătură ale clienților noștri. Acest lucru a fost deosebit de dificil, deoarece identitatea semnării este date extrem de confidențiale care nu ar trebui să fie stocate într-un loc nesecurizat mai mult decât este necesar.

În plus, am dorit să încărcăm aceste identități doar în timpul construirii, fără soluții interproiecte. Acest lucru însemna că identitatea nu ar trebui să fie accesibilă în afara sandbox-ului aplicației (sau al compilației). Am abordat deja această din urmă problemă prin tranziția la VM. Cu toate acestea, mai trebuia să găsim o modalitate de a stoca și încărca identitatea de semnare în VM numai pentru timpul de construire.

Probleme cu Fastlane Match

La acea vreme, încă folosim potrivirea Fastlane, care stochează identitățile și proviziile criptate într-un depozit separat, le încarcă în timpul procesului de construire într-o instanță separată a brelocului și elimină acea instanță după compilare.

Această abordare pare convenabilă, dar are câteva probleme:

  • Pentru a funcționa, necesită întreaga configurație Fastlane.

Fastlane este rubygem și toate problemele enumerate în primul capitol se aplică aici.

  • Verificarea depozitului în timpul construirii.

Ne-am păstrat identitățile într-un depozit separat care a fost verificat în timpul procesului de construire, mai degrabă decât în ​​timpul procesului de configurare. Acest lucru a însemnat că a trebuit să stabilim un acces separat la depozitul de identități, nu doar pentru Gitlab, ci și pentru cei care rulează, similar cu modul în care vom gestiona dependențele private de la terți.

  • Greu de gestionat în afara Meciului.

Dacă utilizați Match pentru gestionarea identităților sau aprovizionarea, nu este nevoie de intervenție manuală. Editarea, decriptarea și criptarea manuală a profilurilor, astfel încât potrivirile să poată funcționa cu ele mai târziu, este plictisitoare și necesită timp. Utilizarea Fastlane pentru a efectua acest proces are ca rezultat, de obicei, ștergerea completă a configurației de furnizare a aplicației și crearea uneia noi.

  • Puțin greu de depanat.

În cazul oricărei probleme de semnare a codului, este posibil să vă fie dificil să determinați identitatea și potrivirea de furnizare care tocmai a fost instalată, deoarece ar trebui să le decodați mai întâi.

  • Preocupările legate de securitate.

Potriviți conturile de dezvoltator accesate folosind acreditările furnizate pentru a face modificări în numele lor. În ciuda faptului că Fastlane este open source, unii clienți l-au refuzat din cauza problemelor de securitate.

  • Nu în ultimul rând, a scăpa de Match ar elimina cel mai mare obstacol în drumul nostru spre a scăpa complet de Fastlane.

Cerințele noastre inițiale au fost următoarele:

  • Încărcarea necesită semnarea identității dintr-un loc sigur, de preferință sub formă de text simplu, și plasarea acesteia în breloc.
  • Acea identitate ar trebui să fie accesibilă prin Xcode.
  • De preferință, parola de identitate, numele brelocului și variabilele parolei brelocului ar trebui să fie setate în scopuri de depanare.

Match avea tot ce ne trebuia, dar implementarea Fastlane doar pentru a folosi Match părea o exagerare, mai ales pentru soluțiile multiplatformă cu propriul sistem de construcție. Ne-am dorit ceva asemănător cu Match, dar fără povara grea Ruby, a fost transportat.

Crearea unei soluții proprii

Așa că ne-am gândit – haideți să scriem asta! Am făcut asta cu MQVMRunner, așa că am putea face asta și aici. De asemenea, am ales Swift pentru a face acest lucru, în principal pentru că am putea obține gratuit o mulțime de API-uri necesare folosind cadrul de securitate Apple.

Desigur, nici nu a mers așa de bine cum era de așteptat.

  • Cadrul de securitate în vigoare.

Cea mai ușoară strategie a fost să apelați comenzile bash așa cum face Fastlane. Cu toate acestea, având la dispoziție cadrul de securitate, ne-am gândit că ar fi mai elegant de utilizat pentru dezvoltare.

  • Lipsa de experienta.

Nu eram foarte experimentați cu cadrul de securitate pentru macOS și s-a dovedit că diferă semnificativ de ceea ce eram obișnuiți pe iOS. Acest lucru ne-a dat înapoi în multe cazuri în care nu eram conștienți de limitările macOS sau am presupus că funcționează la fel ca pe iOS - majoritatea acestor presupuneri erau greșite.

  • Documentare groaznică.

Documentația cadrului de securitate Apple este, pentru a spune ușor, umilă. Este un API foarte vechi care datează de la primele versiuni de OSX și, uneori, am avut impresia că nu a fost actualizat de atunci. O mare parte de cod nu este documentată, dar am anticipat cum funcționează citind codul sursă. Din fericire pentru noi, este open-source.

  • Deprecieri fără înlocuiri.

O bună parte din acest cadru este depreciat; Apple încearcă să se îndepărteze de brelocul tipic „stil macOS” (brelocuri multiple accesibile prin parolă) și să implementeze brelocul „stil iOS” (breloc unic, sincronizat prin iCloud). Așa că l-au depreciat în macOS Yosemite în 2014, dar nu au venit cu niciun înlocuitor pentru acesta în ultimii nouă ani – așa că singurul API disponibil pentru noi, deocamdată, este depreciat deoarece nu există încă unul nou.

Am presupus că identitățile de semnare pot fi stocate ca șiruri de caractere codificate base64 în variabilele Gitlab per proiect. Este sigur, bazat pe proiect și, dacă este setată ca variabilă mascata, poate fi citită și afișată în jurnalele de compilare ca un text non-plain.

Deci, aveam datele de identitate. Trebuie doar să-l punem în breloc. Utilizarea API-ului de securitate După câteva încercări și o mulțime de încercări care au trecut prin documentația cadrului de securitate, am pregătit un prototip de ceva care mai târziu a devenit MQSwiftSign.

Învățați sistemul de securitate macOS, dar pe calea grea

A trebuit să obținem o înțelegere profundă a modului în care funcționează brelocul macOS pentru a ne dezvolta instrumentul. Aceasta a implicat cercetarea modului în care lanțul de chei gestionează articolele, accesul și permisiunile acestora și structura datelor din brelocul. De exemplu, am descoperit că brelocul este singurul fișier macOS pe care sistemul de operare îl ignoră setul ACL. În plus, am aflat că ACL pentru anumite articole de breloc sunt un plist text simplu salvat într-un fișier breloc. Ne-am confruntat cu mai multe provocări pe parcurs, dar am învățat și multe.

O provocare semnificativă pe care am întâlnit-o au fost solicitările. Instrumentul nostru a fost conceput în primul rând pentru a rula pe sistemele CI iOS, ceea ce însemna că nu trebuie să fie interactiv. Nu le-am putut cere utilizatorilor să confirme o parolă pentru CI.

Cu toate acestea, sistemul de securitate macOS este bine conceput, ceea ce face imposibilă editarea sau citirea informațiilor confidențiale, inclusiv identitatea semnăturii, fără permisiunea explicită a utilizatorului. Pentru a accesa o resursă fără confirmare, programul de accesare trebuie inclus în Lista de control al accesului a resursei. Aceasta este o cerință strictă pe care niciun program nu o poate rupe, chiar și programele Apple care vin cu sistemul. Dacă vreun program trebuie să citească sau să editeze o intrare de breloc, utilizatorul trebuie să furnizeze o parolă de breloc pentru a o debloca și, opțional, să o adauge la ACL-ul intrării.

Depășirea provocărilor privind permisiunea utilizatorului

Așadar, a trebuit să găsim o modalitate prin care Xcode să acceseze o identitate configurată de brelocul nostru fără a cere permisiunea unui utilizator folosind promptul pentru parolă. Pentru a face acest lucru, putem schimba lista de control al accesului a unui articol, dar asta necesită și permisiunea utilizatorului – și, desigur, o face. În caz contrar, ar submina întregul punct de a avea ACL. Am încercat să ocolim această protecție – am încercat să obținem același efect ca și cu comanda `security set-key-partition-list`.

După o scufundare profundă în documentația cadrului, nu am găsit niciun API care să permită editarea ACL-ului fără a solicita utilizatorului să furnizeze o parolă. Cel mai apropiat lucru pe care l-am găsit este `SecKeychainItemSetAccess`, care declanșează de fiecare dată un prompt UI. Apoi am făcut o altă scufundare, dar de data aceasta, în cea mai bună documentație, care este codul sursă în sine. Cum a implementat-o ​​Apple?

S-a dovedit că – așa cum era de așteptat – că foloseau un API privat. O metodă numită `SecKeychainItemSetAccessWithPassword` face practic același lucru ca `SecKeychainItemSetAccess`, dar în loc să solicite utilizatorului o parolă, parola este furnizată ca argument pentru o funcție. Desigur, ca API privat, nu este listat în documentație, dar Apple nu are documentația pentru astfel de API-uri, ca și cum nu s-ar putea gândi să creeze o aplicație pentru uz personal sau de întreprindere. Deoarece instrumentul a fost menit să fie doar pentru uz intern, nu am ezitat să folosim API-ul privat. Singurul lucru care trebuia făcut era să pună metoda C în Swift.

Depășirea provocărilor privind permisiunea utilizatorului

Deci, fluxul de lucru final al prototipului a fost următorul:

  1. Creați brelocul deblocat temporar cu blocarea automată dezactivată.
  2. Obțineți și decodificați datele de identitate de semnătură codificate în base64 din variabilele de mediu (promis de Gitlab).
  3. Importați identitatea în brelocul creat.
  4. Setați opțiunile de acces adecvate pentru identitatea importată, astfel încât Xcode și alte instrumente să o poată citi pentru codesign.

Alte upgrade-uri

Prototipul funcționa bine, așa că am identificat câteva caracteristici suplimentare pe care am dori să le adăugăm instrumentului. Scopul nostru a fost să înlocuim în cele din urmă fastlane; am implementat deja acțiunea „match”. Cu toate acestea, fastlane a oferit încă două funcții valoroase pe care nu le aveam încă – instalarea profilului de furnizare și crearea export.plist.

Instalarea profilului de furnizare

Instalarea profilului de furnizare este destul de simplă – se reduce la extragerea UUID-ului profilului și la copierea fișierului în `~/Library/MobileDevice/Provisioning Profiles/` cu UUID ca nume de fișier – și asta este suficient pentru ca Xcode să-l vadă corect. Nu este știință să adăugăm instrumentului nostru un plugin simplu pentru a trece peste directorul furnizat și a face asta pentru fiecare fișier .mobileprovision pe care îl găsește în interior.

Export.plist Creare

Crearea export.plist, totuși, este puțin mai complicată. Pentru a genera un fișier IPA adecvat, Xcode solicită utilizatorilor să furnizeze un fișier plist cu informații specifice colectate din diverse surse – fișierul de proiect, fișierul de drepturi, setările spațiului de lucru etc. Motivul pentru care Xcode poate colecta acele date numai prin intermediul expertului de distribuție, dar nu prin CLI îmi este necunoscut. Cu toate acestea, trebuia să le colectăm folosind API-urile Swift, având doar referințe la proiect/spațiul de lucru și o mică doză de cunoștințe despre cum este construit fișierul de proiect Xcode.

Rezultatul a fost mai bun decât se aștepta, așa că am decis să-l adăugăm ca un alt plugin la instrumentul nostru. De asemenea, l-am lansat ca proiect open source pentru un public mai larg. În acest moment, MQSwiftSign este un instrument multifuncțional care poate fi folosit cu succes ca înlocuitor pentru acțiunile de bază fastlane necesare pentru construirea și distribuirea aplicației dvs. iOS și o folosim în fiecare proiect nostru din Miquido.

Gânduri finale: Succesul

Trecerea de la arhitectura Intel la iOS ARM a fost o sarcină dificilă. Ne-am confruntat cu numeroase obstacole și am petrecut timp semnificativ dezvoltării instrumentelor din cauza lipsei de documentație. Cu toate acestea, în cele din urmă, am stabilit un sistem robust:

  • Doi alergători de gestionat în loc de nouă;
  • Rularea software-ului care se află în întregime sub controlul nostru, fără o tonă de supraîncărcare sub formă de rubygems - am reușit să scăpăm de fastlane sau de orice software terță parte din configurațiile noastre de construcție;
  • MULTE cunoștințe și înțelegere a lucrurilor cărora de obicei nu acordăm atenție – cum ar fi securitatea sistemului macOS și cadrul de securitate în sine, o structură reală a unui proiect Xcode și multe, multe altele.

V-aș încuraja cu plăcere – Dacă aveți dificultăți cu configurarea runner-ului GitLab pentru versiunile iOS, încercați MQVMRunner-ul nostru. Dacă aveți nevoie de ajutor pentru construirea și distribuirea aplicației folosind un singur instrument și nu doriți să vă bazați pe rubygems, încercați MQSwiftSign. Funcționează pentru mine, poate funcționa și pentru tine!