Construya una biblioteca estática (dispositivo + simulador) usando Xcode y SDK 4+

Parece que podemos, en teoría, construir una única biblioteca estática que incluya simulador, iPhone y iPad.

Sin embargo, Apple no tiene documentación sobre esto que pueda encontrar, y las plantillas predeterminadas de Xcode NO están configuradas para hacer esto.

Estoy buscando una técnica simple, portátil y reutilizable que se pueda hacer dentro de Xcode.

Algo de historia:

  • En 2008, solíamos ser capaces de crear librerías estáticas individuales que incluían tanto sim como dispositivo. Apple deshabilitó eso.
  • A lo largo de 2009, hicimos pares de libs estáticas, una para sim, una para dispositivo. Apple también ha desactivado eso.

Referencias

  1. Esta es una gran idea, es un enfoque excelente, pero no funciona: http://www.drobnik.com/touch/2010/04/universal-static-libraries/

    • Hay algunos errores en su script que significa que solo funciona en su máquina: debería usar BUILT_PRODUCTS_DIR y / o BUILD_DIR en lugar de “adivinarlos”)
    • El último Xcode de Apple le impide hacer lo que hizo, simplemente no funciona, debido al cambio (documentado) en la forma en que Xcode procesa los objectives)
  2. Otro interrogador SO preguntó cómo hacerlo SIN xcode, y con respuestas que se centraron en la parte arm6 vs arm7, pero ignoró la parte i386: ¿Cómo compilo una biblioteca estática (grasa) para armv6, armv7 e i386?

    • Desde los últimos cambios de Apple, la parte del simulador ya no es la misma que la diferencia arm6 / arm7, es un problema diferente, ver más arriba).

ALTERNATIVAS:

Copiar / pegar fácilmente la última versión (¡pero las instrucciones de instalación pueden cambiar, ver a continuación!)

La biblioteca de Karl requiere mucho más esfuerzo de configuración, pero una solución mucho mejor a largo plazo (convierte su biblioteca en un Framework).

Úselo, luego modifíquelo para agregar soporte para comstackciones de archivos comprimidos – vea el comentario de @ Frederik a continuación sobre los cambios que está usando para que esto funcione bien con el modo Archivo.


CAMBIOS RECIENTES: 1. Se agregó soporte para iOS 10.x (manteniendo el soporte para plataformas antiguas)

  1. Información sobre cómo usar esta secuencia de comandos con un proyecto integrado en otro proyecto (aunque recomiendo NO hacerlo, Apple tiene un par de errores en Xcode si insertas proyectos dentro de la otra, desde Xcode 3.x hasta Xcode 4.6.x)

  2. Script de bonificación para permitirle incluir paquetes automáticamente (es decir, incluir archivos PNG, archivos PLIST, etc. de su biblioteca) – vea a continuación (desplácese hacia abajo)

  3. ahora es compatible con iPhone5 (utilizando la solución de Apple para los errores en lipo). NOTA: las instrucciones de instalación han cambiado (probablemente pueda simplificar esto cambiando el script en el futuro, pero no quiero arriesgarlo ahora)

  4. La sección “copiar encabezados” ahora respeta la configuración de comstackción para la ubicación de los encabezados públicos (cortesía de Frederik Wallner)

  5. Se agregó configuración explícita de SYMROOT (¿quizás también se necesite establecer OBJROOT?), Gracias a Doug Dickinson


SCRIPT (esto es lo que tienes que copiar / pegar)

Para instrucciones de uso / instalación, ver a continuación

########################################## # # 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 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 

INSTALE LAS INSTRUCCIONES

  1. Crear un proyecto de lib estático
  2. Seleccione el objective
  3. En la pestaña “Configuración de comstackción”, configure “Crear architecture activa solamente” en “NO” (para todos los elementos)
  4. En la pestaña “Crear fases”, seleccione “Agregar … Nueva fase de comstackción … Nueva fase de comstackción de secuencia de ejecución”
  5. Copie / pegue el script (arriba) en el cuadro

… BONIFICACIÓN OPCIONAL:

  1. OPCIONAL: si tiene encabezados en su biblioteca, agréguelos a la fase “Copiar encabezados”
  2. OPCIONAL: … y arrástrelos / suelte desde la sección “Proyecto” a la sección “Público”
  3. OPCIONAL: … y se exportarán AUTOMÁTICAMENTE cada vez que compile la aplicación, en un subdirectorio del directorio “debug-universal” (estarán en usr / local / include)
  4. OPCIONAL: NOTA: si también intenta arrastrar / soltar su proyecto en otro proyecto Xcode, esto expone un error en Xcode 4, donde no puede crear un archivo .IPA si tiene Encabezados públicos en su proyecto de arrastrar / soltar. La solución: no incrustar proyectos xcode (¡demasiados errores en el código de Apple!)

Si no puede encontrar el archivo de salida, aquí hay una solución alternativa:

  1. Agregue el siguiente código al final del script (cortesía de Frederik Wallner): abra “$ {CREATING_UNIVERSAL_DIR}”

  2. Apple borra toda la salida después de 200 líneas. Seleccione su objective, y en la fase de ejecución del script, DEBE desmarcar: “Mostrar variables de entorno en el registro de comstackción”

  3. si está utilizando un directorio personalizado de “comstackción de salida” para XCode4, entonces XCode coloca todos sus archivos “inesperados” en el lugar equivocado.

    1. Construye el proyecto
    2. Haga clic en el último icono a la derecha, en el área superior izquierda de Xcode4.
    3. Seleccione el elemento superior (esta es su “comstackción más reciente”. Apple debería seleccionarlo automáticamente, pero no lo pensó)
    4. en la ventana principal, desplácese hacia abajo. La última línea debería decir: lipo: para la configuración actual (Depuración) que crea el archivo de salida: /Users/blah/Library/Developer/Xcode/DerivedData/AppName-ashwnbutvodmoleijzlncudsekyf/Build/Products/Debug-universal/libTargetName.a

    … esa es la ubicación de tu Build Universal.


Cómo incluir archivos “sin código fuente” en su proyecto (PNG, PLIST, XML, etc.)

  1. Haga todo lo anterior, compruebe que funciona
  2. Cree una nueva fase de Ejecutar script que viene DESPUÉS DEL PRIMERO (copie / pegue el código a continuación)
  3. Crea un nuevo objective en Xcode, del tipo “paquete”
  4. En su PROYECTO PRINCIPAL, en “Fases de comstackción”, agregue el nuevo paquete como algo de lo que “depende” (sección superior, presione el botón más, desplácese hacia abajo, encuentre el archivo “.bundle” en sus Productos)
  5. En su NEW BUNDLE TARGET, en “Build Fases”, agregue una sección de “Copy Bundle Resources” y arrastre / suelte todos los archivos PNG, etc.

Script para copiar automáticamente los paquetes integrados en la misma carpeta que su biblioteca estática 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}" 

He pasado muchas horas tratando de construir una biblioteca estática gorda que funcionará en armv7, armv7s y el simulador. Finalmente encontré una solución .

Lo esencial es construir las dos bibliotecas (una para el dispositivo y luego una para el simulador) por separado, cambiarles el nombre para que se distingan entre sí, y luego lipo -crearlas en una sola biblioteca.

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

¡Lo intenté y funciona!

He creado una plantilla de proyecto XCode 4 que te permite crear un marco universal tan fácilmente como hacer una biblioteca normal.

Hay una utilidad de línea de comandos xcodebuild y puede ejecutar el comando de shell dentro de xcode. Por lo tanto, si no te importa usar un script personalizado, este script puede ayudarte.

 #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}" 

Tal vez parece ineficiente (no soy bueno en el guión de shell), pero fácil de entender. Configuré un nuevo objective ejecutando solo este script. El script está diseñado para línea de comandos pero no probado en 🙂

El concepto central es xcodebuild y lipo .

Intenté muchas configuraciones dentro de Xcode UI, pero nada funcionó. Debido a que este es un tipo de procesamiento por lotes, el diseño de la línea de comandos es más adecuado, por lo que Apple eliminó gradualmente la función de creación de lotes de Xcode. Por lo tanto, no espero que ofrezcan funciones de generación por lotes basadas en UI en el futuro.

Necesitaba una lib estática para JsonKit, así que creé un proyecto lib estático en Xcode y luego ejecuté este script bash en el directorio del proyecto. Siempre que haya configurado el proyecto xcode con “Build active configuration only” desactivada, debe obtener todas las architectures en una lib.

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

Actualización de IOS 10:

Tuve un problema al construir el fatlib con iphoneos10.0 porque la expresión regular en el script solo espera 9.x y más baja y devuelve 0.0 para ios 10.0

para arreglar esto simplemente reemplace

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

con

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

He convertido esto en una plantilla de Xcode 4 , en la misma línea que la plantilla de marco estático de Karl.

Descubrí que la creación de marcos estáticos (en lugar de bibliotecas estáticas simples) estaba causando lockings aleatorios con LLVM, debido a un aparente error del enlazador, así que, supongo que ¡las bibliotecas estáticas todavía son útiles!

¡Gran trabajo! Arreglé algo similar, pero tuve que ejecutarlo por separado. Tenerlo solo como parte del proceso de construcción lo hace mucho más simple.

Un elemento de la nota. Noté que no copia sobre ninguno de los archivos de inclusión que marcas como públicos. He adaptado lo que tenía en mi script al tuyo y funciona bastante bien. Pegue lo siguiente al final de su 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 

De hecho, escribí mi propio guión para este propósito. No usa Xcode. (Se basa en un script similar en el proyecto Gambit Scheme).

Básicamente, ejecuta ./configure y crea tres veces (para i386, armv7 y armv7s), y combina cada una de las bibliotecas resultantes en una lib lib.