Construire une bibliothèque statique volumineuse (périphérique + simulateur) en utilisant Xcode et SDK 4+

Il semble que nous puissions – théoriquement – construire une seule bibliothèque statique comprenant à la fois un simulateur et un iPhone et un iPad.

Cependant, Apple n’a aucune documentation à ce sujet que je puisse trouver, et les modèles par défaut de Xcode ne sont PAS configurés pour cela.

Je recherche une technique simple, portable et réutilisable qui peut être réalisée dans Xcode.

Un peu d’histoire:

  • En 2008, nous étions en mesure de créer des librairies statiques uniques comprenant à la fois sim et device. Apple a désactivé cela.
  • Tout au long de 2009, nous avons créé des paires de bibliothèques statiques – une pour sim, une pour périphérique. Apple a maintenant désactivé cela aussi.

Les références:

  1. C’est une excellente idée, c’est une excellente approche, mais ça ne marche pas: http://www.drobnik.com/touch/2010/04/universal-static-libraries/

    • Il y a des bogues dans son script, cela signifie qu’il ne fonctionne que sur sa machine – il devrait utiliser BUILT_PRODUCTS_DIR et / ou BUILD_DIR au lieu de les “évaluer”
    • Le dernier code X d’Apple vous empêche de faire ce qu’il a fait – cela ne fonctionnera tout simplement pas, en raison du changement (documenté) dans la façon dont Xcode traite les cibles)
  2. Un autre questionneur a demandé comment le faire SANS xcode, et avec des réponses centrées sur la partie arm6 vs arm7 – mais a ignoré la partie i386: Comment comstackr une bibliothèque statique (fat) pour armv6, armv7 et i386

    • Depuis les derniers changements d’Apple, la partie Simulator n’est plus la même que la différence arm6 / arm7 – c’est un problème différent, voir ci-dessus.

ALTERNATIVES:

Copie / collage facile de la dernière version (mais les instructions d’installation peuvent changer – voir ci-dessous!)

La bibliothèque de Karl demande beaucoup plus d’efforts de configuration, mais une solution à long terme beaucoup plus agréable (elle convertit votre bibliothèque en une structure).

Utilisez ceci, puis ajustez-le pour append le support pour les compilations Archive – le commentaire de cf @ Frederik ci-dessous sur les changements qu’il utilise pour que cela fonctionne correctement avec le mode Archive.


MODIFICATIONS RÉCENTES: 1. Ajout du support pour iOS 10.x (tout en maintenant le support pour les anciennes plates-formes)

  1. Informations sur l’utilisation de ce script avec un projet incorporé dans un autre projet (bien que je recommande fortement de ne jamais le faire, Apple a quelques bogues dans Xcode si vous intégrez des projets les uns aux autres, à partir de Xcode) 3.x jusqu’à Xcode 4.6.x)

  2. Script bonus pour vous permettre d’inclure automatiquement les bundles (c.-à-d. Inclure les fichiers PNG, les fichiers PLIST, etc. de votre bibliothèque!) – voir ci-dessous (défiler vers le bas)

  3. prend désormais en charge iPhone5 (en utilisant la solution de contournement Apple pour les bogues dans lipo). REMARQUE: les instructions d’installation ont changé (je peux probablement simplifier cela en changeant le script à l’avenir, mais je ne veux pas risquer maintenant)

  4. La section “copie des en-têtes” respecte désormais le paramètre de construction pour l’emplacement des en-têtes publiques (avec la permission de Frederik Wallner)

  5. Ajout du paramétrage explicite de SYMROOT (peut-être faut-il aussi définir OBJROOT?), Grâce à Doug Dickinson


SCRIPT (c’est ce que vous devez copier / coller)

Pour les instructions d’utilisation / d’installation, voir ci-dessous

########################################## # # cf https://stackoverflow.com/questions/3520977/build-fat-static-library-device-simulator-using-xcode-and-sdk-4 # # Version 2.82 # # Latest Change: # - MORE tweaks to get the iOS 10+ and 9- working # - Support iOS 10+ # - Corrected typo for iOS 1-10+ (thanks @stuikomma) # # Purpose: # Automatically create a Universal static library for iPhone + iPad + iPhone Simulator from within XCode # # Author: Adam Martin - http://twitter.com/redglassesapps # Based on: original script from Eonil (main changes: Eonil's script WILL NOT WORK in Xcode GUI - it WILL CRASH YOUR COMPUTER) # set -e set -o pipefail #################[ Tests: helps workaround any future bugs in Xcode ]######## # DEBUG_THIS_SCRIPT="false" if [ $DEBUG_THIS_SCRIPT = "true" ] then echo "########### TESTS #############" echo "Use the following variables when debugging this script; note that they may change on recursions" echo "BUILD_DIR = $BUILD_DIR" echo "BUILD_ROOT = $BUILD_ROOT" echo "CONFIGURATION_BUILD_DIR = $CONFIGURATION_BUILD_DIR" echo "BUILT_PRODUCTS_DIR = $BUILT_PRODUCTS_DIR" echo "CONFIGURATION_TEMP_DIR = $CONFIGURATION_TEMP_DIR" echo "TARGET_BUILD_DIR = $TARGET_BUILD_DIR" fi #####################[ part 1 ]################## # First, work out the BASESDK version number (NB: Apple ought to report this, but they hide it) # (incidental: searching for subssortingngs in sh is a nightmare! Sob) SDK_VERSION=$(echo ${SDK_NAME} | grep -o '\d\{1,2\}\.\d\{1,2\}$') # Next, work out if we're in SIM or DEVICE if [ ${PLATFORM_NAME} = "iphonesimulator" ] then OTHER_SDK_TO_BUILD=iphoneos${SDK_VERSION} else OTHER_SDK_TO_BUILD=iphonesimulator${SDK_VERSION} fi echo "XCode has selected SDK: ${PLATFORM_NAME} with version: ${SDK_VERSION} (although back-targetting: ${IPHONEOS_DEPLOYMENT_TARGET})" echo "...therefore, OTHER_SDK_TO_BUILD = ${OTHER_SDK_TO_BUILD}" # #####################[ end of part 1 ]################## #####################[ part 2 ]################## # # IF this is the original invocation, invoke WHATEVER other builds are required # # Xcode is already building ONE target... # # ...but this is a LIBRARY, so Apple is wrong to set it to build just one. # ...we need to build ALL targets # ...we MUST NOT re-build the target that is ALREADY being built: Xcode WILL CRASH YOUR COMPUTER if you try this (infinite recursion!) # # # So: build ONLY the missing platforms/configurations. if [ "true" == ${ALREADYINVOKED:-false} ] then echo "RECURSION: I am NOT the root invocation, so I'm NOT going to recurse" else # CRITICAL: # Prevent infinite recursion (Xcode sucks) export ALREADYINVOKED="true" echo "RECURSION: I am the root ... recursing all missing build targets NOW..." echo "RECURSION: ...about to invoke: xcodebuild -configuration \"${CONFIGURATION}\" -project \"${PROJECT_NAME}.xcodeproj\" -target \"${TARGET_NAME}\" -sdk \"${OTHER_SDK_TO_BUILD}\" ${ACTION} RUN_CLANG_STATIC_ANALYZER=NO" BUILD_DIR=\"${BUILD_DIR}\" BUILD_ROOT=\"${BUILD_ROOT}\" SYMROOT=\"${SYMROOT}\" xcodebuild -configuration "${CONFIGURATION}" -project "${PROJECT_NAME}.xcodeproj" -target "${TARGET_NAME}" -sdk "${OTHER_SDK_TO_BUILD}" ${ACTION} RUN_CLANG_STATIC_ANALYZER=NO BUILD_DIR="${BUILD_DIR}" BUILD_ROOT="${BUILD_ROOT}" SYMROOT="${SYMROOT}" ACTION="build" #Merge all platform binaries as a fat binary for each configurations. # Calculate where the (multiple) built files are coming from: CURRENTCONFIG_DEVICE_DIR=${SYMROOT}/${CONFIGURATION}-iphoneos CURRENTCONFIG_SIMULATOR_DIR=${SYMROOT}/${CONFIGURATION}-iphonesimulator echo "Taking device build from: ${CURRENTCONFIG_DEVICE_DIR}" echo "Taking simulator build from: ${CURRENTCONFIG_SIMULATOR_DIR}" CREATING_UNIVERSAL_DIR=${SYMROOT}/${CONFIGURATION}-universal echo "...I will output a universal build to: ${CREATING_UNIVERSAL_DIR}" # ... remove the products of previous runs of this script # NB: this directory is ONLY created by this script - it should be safe to delete! rm -rf "${CREATING_UNIVERSAL_DIR}" mkdir "${CREATING_UNIVERSAL_DIR}" # echo "lipo: for current configuration (${CONFIGURATION}) creating output file: ${CREATING_UNIVERSAL_DIR}/${EXECUTABLE_NAME}" xcrun -sdk iphoneos lipo -create -output "${CREATING_UNIVERSAL_DIR}/${EXECUTABLE_NAME}" "${CURRENTCONFIG_DEVICE_DIR}/${EXECUTABLE_NAME}" "${CURRENTCONFIG_SIMULATOR_DIR}/${EXECUTABLE_NAME}" ######### # # Added: StackOverflow suggestion to also copy "include" files # (untested, but should work OK) # echo "Fetching headers from ${PUBLIC_HEADERS_FOLDER_PATH}" echo " (if you embed your library project in another project, you will need to add" echo " a "User Search Headers" build setting of: (NB INCLUDE THE DOUBLE QUOTES BELOW!)" echo ' "$(TARGET_BUILD_DIR)/usr/local/include/"' if [ -d "${CURRENTCONFIG_DEVICE_DIR}${PUBLIC_HEADERS_FOLDER_PATH}" ] then mkdir -p "${CREATING_UNIVERSAL_DIR}${PUBLIC_HEADERS_FOLDER_PATH}" # * needs to be outside the double quotes? cp -r "${CURRENTCONFIG_DEVICE_DIR}${PUBLIC_HEADERS_FOLDER_PATH}"* "${CREATING_UNIVERSAL_DIR}${PUBLIC_HEADERS_FOLDER_PATH}" fi fi 

INSTALLER LES INSTRUCTIONS

  1. Créer un projet statique
  2. Sélectionnez la cible
  3. Dans l’onglet “Paramètres de construction”, définissez “Construire uniquement l’architecture active” sur “NON” (pour tous les éléments).
  4. Dans l’onglet “Build Phases”, sélectionnez “Add … New Build Phase … Nouvelle phase de construction du script d’exécution”
  5. Copiez / collez le script (ci-dessus) dans la boîte

… BONUS en option:

  1. FACULTATIF: si vous avez des en-têtes dans votre bibliothèque, ajoutez-les à la phase “Copier les en-têtes”
  2. FACULTATIF: … et les faire glisser depuis la section “Projet” vers la section “Public”
  3. FACULTATIF: … et ils seront automatiquement exportés chaque fois que vous construirez l’application, dans un sous-répertoire du répertoire “debug-universal” (ils seront dans usr / local / include)
  4. FACULTATIF: REMARQUE: si vous essayez également de glisser / déposer votre projet dans un autre projet Xcode, cela expose un bogue dans Xcode 4, où il ne peut pas créer un fichier .IPA si vous avez des en-têtes publics dans votre projet glisser / déposer. La solution de contournement: n’incorporez pas de projets xcode (trop de bogues dans le code d’Apple!)

Si vous ne trouvez pas le fichier de sortie, voici une solution de contournement:

  1. Ajoutez le code suivant à la fin du script (avec la permission de Frederik Wallner): ouvrez “$ {CREATING_UNIVERSAL_DIR}”

  2. Apple supprime toutes les sorties après 200 lignes. Sélectionnez votre cible, et dans la phase d’exécution du script, vous devez décocher: “Afficher les variables d’environnement dans le journal de construction”

  3. Si vous utilisez un répertoire personnalisé “build output” pour XCode4, alors XCode place tous vos fichiers “inattendus” au mauvais endroit.

    1. Construire le projet
    2. Cliquez sur la dernière icône à droite, dans la partie supérieure gauche de Xcode4.
    3. Sélectionnez le premier élément (ceci est votre “version la plus récente”. Apple devrait le sélectionner automatiquement, mais ils n’y ont pas pensé)
    4. dans la fenêtre principale, faites défiler vers le bas. La toute dernière ligne doit se lire comme suit: lipo: pour la configuration actuelle (Debug), création du fichier de sortie: /Users/blah/Library/Developer/Xcode/DerivedData/AppName-ashwnbutvodmoleijzlncudsekyf/Build/Products/Debug-universal/libTargetName.a

    … c’est l’emplacement de votre construction universelle.


Comment inclure des fichiers “non sourcecode” dans votre projet (PNG, PLIST, XML, etc.)

  1. Faites tout ce qui précède, vérifiez qu’il fonctionne
  2. Créer une nouvelle phase d’exécution du script APRÈS LE PREMIER (copier / coller le code ci-dessous)
  3. Créer une nouvelle cible dans Xcode, de type “bundle”
  4. Dans votre MAIN PROJECT, dans “Build Phases”, ajoutez le nouveau bundle comme “dépendant” (section du haut, appuyez sur le bouton plus, faites défiler jusqu’au bas, trouvez le fichier “.bundle” dans vos produits)
  5. Dans votre NEW BUNDLE TARGET, dans “Build Phases”, ajoutez une section “Copy Bundle Resources” et glissez / déposez tous les fichiers PNG, etc.

Script pour copier automatiquement le ou les ensembles créés dans le même dossier que votre bibliothèque statique FAT:

 echo "RunScript2:" echo "Autocopying any bundles into the 'universal' output folder created by RunScript1" CREATING_UNIVERSAL_DIR=${SYMROOT}/${CONFIGURATION}-universal cp -r "${BUILT_PRODUCTS_DIR}/"*.bundle "${CREATING_UNIVERSAL_DIR}" 

J’ai passé de nombreuses heures à essayer de créer une bibliothèque statique volumineuse qui fonctionne sur armv7, armv7s et le simulateur. Enfin trouvé une solution .

L’essentiel est de construire séparément les deux bibliothèques (une pour le périphérique, puis une pour le simulateur), de les renommer pour les distinguer les unes des autres, puis de les créer en une seule bibliothèque.

 lipo -create libPhone.a libSimulator.a -output libUniversal.a 

Je l’ai essayé et il fonctionne!

J’ai créé un modèle de projet XCode 4 qui vous permet de créer un cadre universel aussi facilement que de créer une bibliothèque classique.

Il existe un utilitaire de ligne de commande xcodebuild et vous pouvez exécuter la commande shell dans xcode. Donc, si cela ne vous dérange pas d’utiliser un script personnalisé, ce script peut vous aider.

 #Configurations. #This script designed for Mac OS X command-line, so does not use Xcode build variables. #But you can use it freely if you want. TARGET=sns ACTION="clean build" FILE_NAME=libsns.a DEVICE=iphoneos3.2 SIMULATOR=iphonesimulator3.2 #Build for all platforms/configurations. xcodebuild -configuration Debug -target ${TARGET} -sdk ${DEVICE} ${ACTION} RUN_CLANG_STATIC_ANALYZER=NO xcodebuild -configuration Debug -target ${TARGET} -sdk ${SIMULATOR} ${ACTION} RUN_CLANG_STATIC_ANALYZER=NO xcodebuild -configuration Release -target ${TARGET} -sdk ${DEVICE} ${ACTION} RUN_CLANG_STATIC_ANALYZER=NO xcodebuild -configuration Release -target ${TARGET} -sdk ${SIMULATOR} ${ACTION} RUN_CLANG_STATIC_ANALYZER=NO #Merge all platform binaries as a fat binary for each configurations. DEBUG_DEVICE_DIR=${SYMROOT}/Debug-iphoneos DEBUG_SIMULATOR_DIR=${SYMROOT}/Debug-iphonesimulator DEBUG_UNIVERSAL_DIR=${SYMROOT}/Debug-universal RELEASE_DEVICE_DIR=${SYMROOT}/Release-iphoneos RELEASE_SIMULATOR_DIR=${SYMROOT}/Release-iphonesimulator RELEASE_UNIVERSAL_DIR=${SYMROOT}/Release-universal rm -rf "${DEBUG_UNIVERSAL_DIR}" rm -rf "${RELEASE_UNIVERSAL_DIR}" mkdir "${DEBUG_UNIVERSAL_DIR}" mkdir "${RELEASE_UNIVERSAL_DIR}" lipo -create -output "${DEBUG_UNIVERSAL_DIR}/${FILE_NAME}" "${DEBUG_DEVICE_DIR}/${FILE_NAME}" "${DEBUG_SIMULATOR_DIR}/${FILE_NAME}" lipo -create -output "${RELEASE_UNIVERSAL_DIR}/${FILE_NAME}" "${RELEASE_DEVICE_DIR}/${FILE_NAME}" "${RELEASE_SIMULATOR_DIR}/${FILE_NAME}" 

Peut-être que ça a l’air inefficace (je ne suis pas doué pour le script shell), mais facile à comprendre. J’ai configuré une nouvelle cible en exécutant uniquement ce script. Le script est conçu pour la ligne de commande mais pas testé 🙂

Le concept de base est xcodebuild et lipo .

J’ai essayé de nombreuses configurations dans l’interface utilisateur Xcode, mais rien n’a fonctionné. Comme il s’agit d’une sorte de traitement par lots, la conception de la ligne de commande est donc plus adaptée, de sorte qu’Apple a progressivement supprimé la fonctionnalité de génération par lot de Xcode. Je ne m’attends donc pas à ce qu’ils offrent une fonctionnalité de génération par lots basée sur l’interface utilisateur à l’avenir.

J’avais besoin d’une librairie statique volumineuse pour JsonKit, alors j’ai créé un projet de lib statique dans Xcode, puis j’ai exécuté ce script bash dans le répertoire du projet. Tant que vous avez configuré le projet xcode avec “Build active configuration only” désactivé, vous devriez obtenir toutes les architectures dans une seule bibliothèque.

 #!/bin/bash xcodebuild -sdk iphoneos xcodebuild -sdk iphonesimulator lipo -create -output libJsonKit.a build/Release-iphoneos/libJsonKit.a build/Release-iphonesimulator/libJsonKit.a 

IOS 10 Update:

J’ai eu un problème avec la construction du fatlib avec iphoneos10.0 car l’expression régulière dans le script n’attend que 9.x et plus bas et renvoie 0.0 pour ios 10.0

pour résoudre ce problème, remplacez simplement

 SDK_VERSION=$(echo ${SDK_NAME} | grep -o '.\{3\}$') 

avec

 SDK_VERSION=$(echo ${SDK_NAME} | grep -o '[\\.0-9]\{3,4\}$') 

Je l’ai fait dans un modèle Xcode 4 , dans la même veine que le modèle de framework statique de Karl.

J’ai trouvé que la construction de frameworks statiques (au lieu de bibliothèques statiques simples) provoquait des plantages aléatoires avec LLVM, à cause d’un bug apparent de l’éditeur de liens – donc, je suppose que les bibliothèques statiques sont toujours utiles!

Bon travail! J’ai piraté ensemble quelque chose de similaire, mais j’ai dû l’exécuter séparément. Le faire simplement faire partie du processus de construction le rend beaucoup plus simple.

Un élément de note J’ai remarqué qu’il ne copie aucun des fichiers d’inclusion que vous marquez comme public. J’ai adapté ce que j’avais dans mon script à la votre et cela fonctionne assez bien. Collez le texte suivant à la fin de votre script.

 if [ -d "${CURRENTCONFIG_DEVICE_DIR}/usr/local/include" ] then mkdir -p "${CURRENTCONFIG_UNIVERSAL_DIR}/usr/local/include" cp "${CURRENTCONFIG_DEVICE_DIR}"/usr/local/include/* "${CURRENTCONFIG_UNIVERSAL_DIR}/usr/local/include" fi 

Je viens juste d’ écrire mon propre script à cette fin. Il n’utilise pas Xcode. (Il est basé sur un script similaire dans le projet Gambit Scheme.)

Fondamentalement, il exécute ./configure et crée trois fois (pour i386, armv7 et armv7s), et combine chacune des bibliothèques résultantes en une grosse lib.