Come aggiungere un framework Swift dinamico a uno strumento da riga di comando

Vediamo come ho provato ad aggiungere un framework dinamico al mio strumento da riga di comando e discutiamo di cosa è andato storto ad ogni passaggio.

Passaggio 1: aggiungilo alla sezione "Strutture e librerie collegate"

Ecco cosa succede quando esegui la tua app:

dyld: libreria non caricata: @ rpath / libswiftAppKit.dylib
Citato da: /Users/seanberry/Library/Developer/Xcode/DerivedData/TestCommandLineTool-fnrmhjvjmugvqueaqvbklzwhqvuv/Build/Products/Debug/ThirdParty.framework/Versions/A/Third
Motivo: immagine non trovata

ThirdParty.framework sta cercando di trovare libswiftAppKit.dylib (che fa parte delle librerie standard di Swift) nella directory @rpath.

Possiamo vedere come ThirdParty.framework definisce @rpath eseguendolo

$ oTool -l ThirdParty.framework / Versions / Current / ThirdParty
Carica comando 27
cmd LC_RPATH
cmdsize 48
path @executable_path /../ Frameworks (offset 12)
Carica comando 28
cmd LC_RPATH
cmdsize 40
path @ loader_path / Frameworks (offset 12)

Bene spara. Non sono rilevanti per il nostro strumento da riga di comando. Non abbiamo cartelle denominate / Frameworks o ../Frameworks. Perché sta cercando le librerie standard di Swift lì?

Perché è progettato per le app iOS e Mac. Ecco la struttura delle directory all'interno di un'app per Mac:

Questo spiega il percorso @executable_path /../ Frameworks

E per iOS, la struttura delle directory all'interno dell'app è:

@loader_path è uguale a @executable_path quando il framework viene caricato dall'eseguibile

E questo spiega path @ loader_path / Frameworks (offset 12)

Ma che dire di noi, l'umile sviluppatore dello strumento da riga di comando? Le librerie standard di Swift sono collegate staticamente all'interno del nostro eseguibile, ma i nostri framework di terze parti non le trovano. Purtroppo non sono archiviati in un posto standard su tutti i Mac. Gli sviluppatori possono accedervi seppelliti all'interno di Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/macosx (ma non richiedere agli utenti di installare Xcode).

Quindi cosa facciamo? La pratica standard che ho visto è quella di creare un framework personalizzato per il tuo progetto, importare in esso tutte le tue dipendenze, quindi copiare anche le veloci librerie standard.

Passaggio 2: creare un framework personalizzato e copiare le librerie Swift standard

Questo copierà / incollerà le librerie standard di Swift all'interno di FirstParty.framework / Versions / Current / Frameworks /Assicurati di copiare anche nella tua dipendenza di terze partiOra i tuoi quadri sono tutti insieme!

Perfetto! Ora andiamo avanti ed eseguiamo il nostro strumento da riga di comando ...

objc [2335]: la classe _TtC8Dispatch16DispatchWorkItem è implementata in entrambi /Users/seanberry/Library/Developer/Xcode/DerivedData/TestCommandLineTool-fnrmhjvjmugvqueaqvb/d//d//dir/prodies/Frame/Prod/file/prod/file/prod/file/prod/file/prod/file/prod/file/prod/prod/file/prod/prod/prod/d=0 e / Users / seanberry / Library / Developer / Xcode / DerivedData / TestCommandLineTool-fnrmhjvjmugvqueaqvbklzwhqvuv / Build / Products / Debug / TestCommandLineTool (0x1005c7698). Verrà utilizzato uno dei due. Quale non è definito.
RIPETI L'ERRORE SOPRA IN 20 DIVERSI MODI

La tua app sta ora vedendo due diverse copie delle librerie standard di Swift: quelle collegate staticamente all'interno del tuo eseguibile e quelle all'interno della cartella / Frameworks.

Ci sono due soluzioni da qui.

Passaggio 3 (opzione A): crea invece un'app per Mac ed estrai l'eseguibile

Ho studiato i famosi strumenti da riga di comando Carthage e SwiftLint per vedere come hanno gestito questo problema. Si scopre che non sono configurati come strumenti da riga di comando! Sono app per Mac! Perché? Perché un'app per Mac non collega staticamente le librerie standard. Si distribuiscono come strumenti da riga di comando aggiungendo una fase di esecuzione che estrae l'eseguibile dal pacchetto dell'app.

#! / Bin / bash
## Estrae lo strumento CLI carthage dal suo pacchetto di applicazioni. Pensato per essere eseguito
# come parte di una fase di creazione di Xcode Run Script.
cp -v "$ {BUILT_PRODUCTS_DIR} / $ {EXECUTABLE_PATH}" "$ {BUILT_PRODUCTS_DIR} / $ {EXECUTABLE_NAME}"

Puoi andare avanti e farlo in questo modo, nessun problema. Ma ho trovato un altro modo per aggirare questo problema.

Passaggio 3 (opzione B): disabilitare il collegamento statico

Aggiungi questi nelle impostazioni di costruzione definite dall'utente:

SWIFT_FORCE_DYNAMIC_LINK_STDLIB SÌ
SWIFT_FORCE_STATIC_LINK_STDLIB NO

Questo costringerà il tuo eseguibile a collegare dinamicamente tutte le librerie. Assicurati di dire a Xcode dove trovarli aggiungendo la directory del framework a "Percorsi di ricerca del percorso"

@ executable_path / FirstParty.framework / versioni / corrente / Frameworks

Buona fortuna con il tuo strumento da riga di comando!

Sean cerca di compilare Xcode su Livefront