web-dev-qa-db-fra.com

Construisez une bibliothèque statique importante (appareil + simulateur) à l'aide de Xcode et du SDK 4+

En théorie, il semble que nous puissions créer une bibliothèque statique unique comprenant à la fois un simulateur, un iPhone et un iPad.

Cependant, Apple ne dispose d'aucune documentation à ce sujet, 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 pouvant être utilisée dans Xcode.

Un peu d'histoire:

  • En 2008, nous pouvions créer une seule bibliothèque statique comprenant à la fois sim et device. Apple l'a désactivée.
  • Tout au long de 2009, nous avons créé des paires de bibliothèques statiques - une pour sim, une pour périphérique. Apple l'a également désactivé.

Références:

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

    • Il y a quelques bugs dans son script qui signifient que cela ne fonctionne que sur sa machine - il devrait utiliser BUILT_PRODUCTS_DIR et/ou BUILD_DIR au lieu de "les guesstimer".
    • Le dernier Xcode 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 SO interrogateur a demandé comment le faire SANS xcode, et avec des réponses qui se concentraient sur la partie arm6 vs arm7 - mais ignorait la partie i386: Comment compiler 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)
278
Adam

ALTERNATIVES:

Copier/coller facile de la dernière version (mais les instructions d'installation peuvent changer - voir ci-dessous!)

La bibliothèque de Karl nécessite beaucoup plus d'efforts de configuration, mais une solution beaucoup plus agréable à long terme (convertit votre bibliothèque en Framework).

tilisez ceci, puis ajustez-le pour ajouter le support pour les versions d'archives - c.f. Le commentaire de Frederik ci-dessous sur les modifications qu'il utilise pour que cela fonctionne bien avec le mode Archive.


MODIFICATIONS RÉCENTES: 1. Prise en charge ajoutée pour iOS 10.x (tout en conservant la prise en charge des plates-formes plus anciennes)

  1. Informations sur l'utilisation de ce script avec un projet intégré dans un autre projet (bien que je vous recommande vivement de NE PAS le faire, jamais - Apple a quelques bogues bloquants dans Xcode si vous intégrez des projets l’un dans l’autre, de Xcode 3.x à Xcode 4.6.x)

  2. Bonus script pour vous permettre d’inclure automatiquement des ensembles (c.-à-d. Inclure des fichiers PNG, PLIST, etc. de votre bibliothèque!) - voir ci-dessous (défilement vers le bas)

  3. supporte maintenant iPhone5 (en utilisant la solution de contournement d'Apple aux bugs de la lipo). NOTE: les instructions d'installation ont changé (je peux probablement simplifier cela en changeant le script à l'avenir, mais je ne veux pas le risquer maintenant)

  4. La section "Copier les en-têtes" respecte désormais les paramètres de construction de l'emplacement des en-têtes publics (avec la permission de Frederik Wallner).

  5. Ajout du réglage explicite de SYMROOT (peut-être besoin d’OBROOT à régler aussi?), Grâce à Doug Dickinson


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

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

##########################################
#
# c.f. 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 substrings 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

INSTRUCTIONS D'INSTALLATION

  1. Créer un projet de bibliothèque statique
  2. Sélectionnez la cible
  3. Dans l'onglet "Paramètres de construction", définissez "Générer l'architecture active uniquement" sur "NON" (pour tous les éléments ).
  4. Dans l'onglet "Build Phases", sélectionnez "Ajouter ... Nouvelle phase de construction ... Nouvelle phase de construction du script d'exécution"
  5. Copier/coller le script (ci-dessus) dans la boîte

... BONUS OPTIONAL use:

  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 faites-les glisser de la section "Projet" vers la section "Public"
  3. FACULTATIF: ... et ils seront automatiquement exportés chaque fois que vous construisez l'application, dans un sous-répertoire du répertoire "debug-universal" (ils seront dans usr/local/include)
  4. FACULTATIF: NOTE: 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 de Fichier .IPA si vous avez des en-têtes publics dans votre projet déposé/déposé. La solution de contournement: n'intégrez pas de projets xcode (trop de bugs 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 toute 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, en haut à gauche de Xcode4.
    3. Sélectionnez l'élément le plus haut (c'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 dernière ligne doit être la suivante: lipo: pour la configuration actuelle (Debug) créant le fichier de sortie: /Users/blah/Library/Developer/Xcode/DerivedData/AppName-ashwnbutvodmoleijzlncudsekyf/Build/Products/Debug-universal/libTargetal.l

    ... c'est l'emplacement de votre construction universelle.


Comment inclure des fichiers "sans code source" dans votre projet (PNG, PLIST, XML, etc.)

  1. Faites tout ce qui précède, vérifiez que cela fonctionne
  2. Créer une nouvelle phase d’exécution de script APRÈS LA PREMIÈRE (copier/coller le code ci-dessous)
  3. Créer une nouvelle cible dans Xcode, de type "bundle"
  4. Dans votre PROJET PRINCIPAL, dans "Phases de construction", ajoutez le nouvel ensemble en tant que "dépend" de celui-ci (section supérieure, appuyez sur le bouton plus, faites défiler vers le bas, recherchez le fichier ".bundle" dans vos produits).
  5. Dans votre nouvelle cible, dans "phases de construction", ajoutez une section "Copier les ressources du paquet", et faites-y glisser/déposer tous les fichiers PNG, etc.

Script pour copier automatiquement les ensembles construits 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}"
270
Adam

J'ai passé de nombreuses heures à essayer de créer une grosse bibliothèque statique qui fonctionnerait avec armv7, armv7s et le simulateur. Enfin trouvé une solution .

Le Gist consiste à construire les deux bibliothèques (une pour le périphérique, puis une pour le simulateur) séparément, en les renommant pour les distinguer les unes des autres, puis en les créant par lipo dans une bibliothèque.

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

Je l'ai essayé et il fonctionne!

80
g_low

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 normale.

74
Karl

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 semble inefficace (je ne suis pas bon en 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 n'est pas testé dans :)

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’un type de traitement par lots, la conception de ligne de commande est donc plus appropriée. Apple a donc supprimé progressivement la fonctionnalité de génération de lots de Xcode. Je ne m'attends donc pas à ce qu'ils offrent la fonctionnalité de génération par lots basée sur l'interface utilisateur à l'avenir.

30
Eonil

J'avais besoin d'une grosse bibliothèque statique pour JsonKit. J'ai donc créé un projet de bibliothèque statique dans Xcode, puis exécuté ce script bash dans le répertoire du projet. Tant que vous avez configuré le projet xcode avec l'option "Construire la configuration active uniquement" désactivée, vous devez 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
9
Brad Robinson

Mise à jour IOS 10:

J'ai eu un problème avec la construction de fatlib avec iphoneos10.0 parce que l'expression régulière dans le script n'attend que 9.x et moins et renvoie 0.0 pour iOS 10.0

pour résoudre ce problème, il suffit de remplacer

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

avec

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

J'ai transformé cela en un modèle Xcode 4 , dans la même veine que le modèle de cadre statique de Karl.

J'ai constaté que la construction de frameworks statiques (au lieu de bibliothèques statiques simples) provoquait des plantages aléatoires avec LLVM, en raison d'un bogue apparent de l'éditeur de liens - donc, je suppose que les bibliothèques statiques sont toujours utiles!

4
Michael Tyson

Bon travail! J'ai piraté quelque chose de similaire, mais je devais l'exécuter séparément. Le simple fait de faire partie du processus de construction le rend beaucoup plus simple.

Un élément à noter. J'ai remarqué qu'il ne copie aucun des fichiers include que vous avez marqués comme publics. J'ai adapté ce que j'avais dans mon script au vôtre et cela fonctionne assez bien. Collez ce qui suit à 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
2
user503821

En fait, j'ai juste écrit 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 and make trois fois (pour i386, armv7 et armv7s), et combine chacune des bibliothèques résultantes dans une fat lib.

1
whooops