Crie biblioteca estática de gordura (dispositivo + simulador) usando o Xcode e o SDK 4+

Parece que podemos – teoricamente – construir uma única biblioteca estática que inclui simulador e iPhone e iPad.

No entanto, a Apple não tem documentação sobre isso que eu possa encontrar, e os modelos padrão do Xcode NÃO estão configurados para isso.

Estou procurando uma técnica simples, portátil e reutilizável que possa ser feita dentro do Xcode.

Alguma história:

  • Em 2008, éramos capazes de fazer bibliotecas estáticas únicas que incluíam sim e dispositivo. A Apple desativou isso.
  • Ao longo de 2009, fizemos pares de libs estáticas – uma para sim, uma para dispositivo. A Apple também desativou isso também.

Referências:

  1. Essa é uma ótima idéia, é uma excelente abordagem, mas não funciona: http://www.drobnik.com/touch/2010/04/universal-static-libraries/

    • Existem alguns bugs em seu script que significa que ele só funciona em sua máquina – ele deve estar usando BUILT_PRODUCTS_DIR e / ou BUILD_DIR ao invés de “adivinhar” eles)
    • O último Xcode da Apple evita que você faça o que ele fez – simplesmente não funcionará, devido à mudança (documentada) de como o Xcode processa os alvos)
  2. Outro entrevistador SO perguntou como fazê-lo sem xcode, e com respostas que se concentraram na parte arm6 vs arm7 – mas ignorou a parte i386: Como faço para compilar uma biblioteca estática (gordura) para armv6, armv7 e i386

    • Desde as últimas alterações da Apple, a parte do Simulador não é mais a mesma do que a diferença arm6 / arm7 – é um problema diferente, veja acima)

   

ALTERNATIVAS:

Fácil copiar / colar da última versão (mas as instruções de instalação podem mudar – veja abaixo!)

A biblioteca de Karl tem muito mais esforço para configurar, mas uma solução de longo prazo muito melhor (ela converte sua biblioteca em um Framework).

Use isso e ajuste-o para adicionar suporte a compilações de Arquivos – o comentário de cf @ Frederik abaixo sobre as alterações que ele está usando para fazer isso funcionar bem com o modo Arquivo.


MUDANÇAS RECENTES: 1. Adicionado suporte para iOS 10.x (mantendo o suporte para plataformas mais antigas)

  1. Informações sobre como usar este script com um projeto embutido em outro projeto (embora eu recomendo que você NÃO faça isso, nunca – a Apple tem alguns bugs de show-stopper no Xcode se você integrar projetos dentro do outro, a partir do Xcode 3.x até o Xcode 4.6.x)

  2. Script de bônus para permitir a inclusão automática de bundles (por exemplo, include arquivos PNG, arquivos PLIST, etc. de sua biblioteca!) – veja abaixo (role para baixo)

  3. agora suporta iPhone5 (usando a solução da Apple para os erros no lipo). NOTA: as instruções de instalação foram alteradas (provavelmente eu posso simplificar isso alterando o script no futuro, mas não quero arriscar agora)

  4. A seção “copiar headers” agora respeita a configuração de construção para a localização dos headers públicos (cortesia de Frederik Wallner)

  5. Adicionado configuração explícita de SYMROOT (talvez precise de OBJROOT para ser definido também?), Graças a Doug Dickinson


SCRIPT (isto é o que você tem que copiar / colar)

Para instruções de uso / instalação, veja abaixo

########################################## # # 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 AS INSTRUÇÕES

  1. Crie um projeto de lib de estática
  2. Selecione o alvo
  3. Na guia “Build Settings”, defina “Build Active Architecture Only” como “NO” (para todos os itens)
  4. Na aba “Build Phases”, selecione “Adicionar … Nova Fase de Construção … Nova Fase de Construção de Script de Execução”
  5. Copie / cole o script (acima) na checkbox

… Uso OPCIONAL DO BÔNUS:

  1. OPCIONAL: se você tiver headers na sua biblioteca, adicione-os à fase “Copiar Cabeçalhos”
  2. OPCIONAL: … e arraste / solte-os da seção “Projeto” para a seção “Público”
  3. OPCIONAL: … e eles serão AUTOMATICAMENTE exportados toda vez que você criar o aplicativo, em um subdiretório do diretório “debug-universal” (eles estarão em usr / local / include)
  4. OPCIONAL: OBSERVAÇÃO: se você também tentar arrastar / soltar seu projeto em outro projeto Xcode, isso expõe um bug no Xcode 4, onde ele não pode criar um arquivo .IPA se você tiver Cabeçalhos Públicos no projeto arrastar / soltar. A solução alternativa: não inclua xcode projetos (muitos erros no código da Apple!)

Se você não consegue encontrar o arquivo de saída, aqui está uma solução alternativa:

  1. Adicione o seguinte código ao final do script (cortesia de Frederik Wallner): abra “$ {CREATING_UNIVERSAL_DIR}”

  2. A Apple exclui todas as saídas após 200 linhas. Selecione seu destino e, na fase de script de execução, você deve desmarque: “Mostrar variables ​​de ambiente no log de compilation”

  3. Se você estiver usando um diretório “build output” personalizado para o XCode4, o XCode colocará todos os seus arquivos “inesperados” no lugar errado.

    1. Construa o projeto
    2. Clique no último ícone à direita, na área superior esquerda do Xcode4.
    3. Selecione o item principal (essa é a sua “versão mais recente”. A Apple deve selecioná-lo automaticamente, mas eles não pensaram nisso)
    4. na janela principal, role para baixo. A última linha deve ser: lipo: para configuração atual (Debug), criando o arquivo de saída: /Users/blah/Library/Developer/Xcode/DerivedData/AppName-ashwnbutvodmoleijzlncudsekyf/Build/Products/Debug-universal/libTargetName.a

    … essa é a localização da sua Universal Build.


Como include arquivos “non sourcecode” em seu projeto (PNG, PLIST, XML, etc)

  1. Faça tudo acima, verifique se funciona
  2. Criar uma nova fase Run Script que vem DEPOIS DO PRIMEIRO (copiar / colar o código abaixo)
  3. Criar um novo alvo no Xcode, do tipo “pacote”
  4. Em seu MAIN PROJECT, em “Build Phases”, adicione o novo pacote como algo que “depende” (seção superior, aperte o botão mais, role para baixo, encontre o arquivo “.bundle” em seus produtos)
  5. Em seu NOVO PACOTE TARGET, em “Build Phases”, adicione uma seção “Copy Bundle Resources”, e arraste / solte todos os arquivos PNG, etc.

Script para copiar automaticamente o (s) pacote (s) incorporado (s) na mesma pasta que a sua 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}" 

Eu passei muitas horas tentando construir uma biblioteca estática de gordura que funcionasse em armv7, armv7s e no simulador. Finalmente encontrei uma solução .

A essência é construir as duas bibliotecas (uma para o dispositivo e depois outra para o simulador) separadamente, renomeá-las para distinguir umas das outras e então criá-las em uma biblioteca.

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

Eu tentei e funciona!

Eu criei um modelo de projeto XCode 4 que permite criar uma estrutura universal com a mesma facilidade que criar uma biblioteca comum.

Existe um utilitário de linha de comando xcodebuild e você pode executar o comando shell dentro do xcode. Então, se você não se importa em usar um script personalizado, este script pode ajudá-lo.

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

Talvez pareça ineficiente (não sou bom em shell script), mas é fácil de entender. Eu configurei um novo alvo executando apenas este script. O script foi projetado para linha de comando, mas não foi testado em 🙂

O conceito central é xcodebuild e lipo .

Eu tentei muitas configurações dentro do Xcode UI, mas nada funcionou. Como esse é um tipo de processamento em lote, o design da linha de comando é mais adequado, de modo que a Apple removeu o recurso de compilation em lote do Xcode gradualmente. Portanto, não espero que eles ofereçam um recurso de criação de lote baseado em UI no futuro.

Eu precisava de um lib estático gordo para o JsonKit então criei um projeto de lib estático no Xcode e então executei este script bash no diretório do projeto. Desde que você tenha configurado o projeto xcode com “Build active configuration only” desativado, você deve obter todas as arquiteturas em uma biblioteca.

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

Atualização do IOS 10:

Eu tive um problema com a construção do fatlib com iphoneos10.0 porque a expressão regular no script só espera 9.xe menor e retorna 0.0 para ios 10.0

para consertar isso basta replace

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

com

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

Eu fiz isso em um modelo Xcode 4 , na mesma linha que o modelo de estrutura estática de Karl.

Descobri que criar estruturas estáticas (em vez de bibliotecas estáticas simples) estava causando falhas aleatórias com o LLVM, devido a um aparente erro de vinculador – então, acho que as bibliotecas estáticas ainda são úteis!

Bom trabalho! Eu hackeei algo semelhante, mas tive que executá-lo separadamente. Ter apenas parte do processo de construção torna tudo muito mais simples.

Um item de nota. Percebi que não copia nenhum dos arquivos de inclusão marcados como públicos. Eu adaptei o que eu tinha no meu script para o seu e funciona muito bem. Cole o seguinte no final do seu 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 

Na verdade, acabei de escrever meu próprio roteiro para esse propósito. Não usa o Xcode. (É baseado em um script similar no projeto Gambit Scheme.)

Basicamente, ele roda ./configure e faz três vezes (para i386, armv7 e armv7s) e combina cada uma das bibliotecas resultantes em um lib.