Suponha que eu tenha feito um aplicativo osX sem usar o Xcode. Após compilar com o GCC, recebo um executável que está vinculado a várias outras bibliotecas. Algumas dessas bibliotecas podem ser novamente vinculadas dinamicamente a outras bibliotecas do sistema não padrão
Existe alguma ferramenta que faz um pacote OSX App criando primeiro as estruturas de diretório necessárias e, em seguida, copiando / verificando / corrigindo recursivamente os links para garantir que todas as dependências dinâmicas também estejam no pacote do app?
Acho que posso tentar escrever algo assim, mas gostaria de saber se algo assim já existe.
Respostas:
Existem duas maneiras de criar um pacote de aplicativos no MacOSX, o Easy e o Ugly.
A maneira mais fácil é usar o XCode. Feito.
O problema é que às vezes você não consegue.
No meu caso, estou construindo um aplicativo que cria outros aplicativos. Não posso presumir que o usuário tenha o XCode instalado. Também estou usando MacPorts para construir as bibliotecas das quais meu aplicativo depende. Preciso ter certeza de que esses dylibs sejam incluídos no aplicativo antes de distribuí-lo.
Isenção de responsabilidade: eu não estou totalmente qualificado para escrever este post, tudo nele foi mostrado nos documentos da Apple, separando aplicativos existentes e tentativa e erro. Funciona para mim, mas provavelmente está errado. Por favor, me envie um email se você tiver alguma correção.
A primeira coisa que você deve saber é que um pacote de aplicativos é apenas um diretório.
Vamos examinar a estrutura de um foo.app hipotético.
Info.plist é um arquivo XML simples. Você pode editá-lo com um editor de texto ou o aplicativo Property List Editor que vem com o XCode. (Está no diretório / Desenvolvedor / Aplicativos / Utilitários /).
Os principais itens que você precisa incluir são:
CFBundleName - O nome do aplicativo.
CFBundleIcon - Um arquivo de ícone que se presume estar no diretório Conteúdo / Recursos. Use o aplicativo Icon Composer para criar o ícone. (Também está no diretório / Developer / Applications / Utilities /). Você pode simplesmente arrastar e soltar um png em sua janela e deve gerar automaticamente os níveis de mip para você.
CFBundleExecutable - Nome do arquivo executável que se presume estar em Contents / MacOS / subpasta.
Existem muito mais opções, as listadas acima são apenas o mínimo. Aqui estão algumas documentações da Apple sobre o arquivo Info.plist e a estrutura do pacote de aplicativos .
Além disso, aqui está um exemplo de Info.plist.
Em um mundo perfeito, você poderia simplesmente soltar seu executável no diretório Contents / MacOS / e pronto. No entanto, se seu aplicativo tiver dependências dylib fora do padrão, ele não funcionará. Como o Windows, o MacOS vem com seu próprio tipo especial de DLL Hell .
Se você estiver usando MacPorts para construir bibliotecas às quais vincular, os locais dos dylibs serão embutidos em código em seu executável. Se você executar o aplicativo em uma máquina que tenha os dylibs exatamente no mesmo local, ele funcionará bem. No entanto, a maioria dos usuários não os instalará; quando eles clicarem duas vezes em seu aplicativo, ele simplesmente travará.
Antes de distribuir seu executável, você precisará coletar todos os dylibs que carrega e copiá-los para o pacote de aplicativos. Você também precisará editar o executável para que procure os dylibs no local correto. ou seja, para onde você os copiou.
Editar manualmente um executável parece perigoso, certo? Felizmente, existem ferramentas de linha de comando para ajudar.
Este comando listará todos os dylibs dos quais seu aplicativo depende. Se você vir algum que NÃO esteja na pasta System / Library ou usr / lib, esses são os que você precisa copiar para o pacote de aplicativos. Copie-os para a pasta / Contents / MacOS /. Em seguida, você precisará editar o executável para usar os novos dylibs.
Primeiro, você precisa ter certeza de vincular usando o sinalizador -headerpad_max_install_names. Isso apenas garante que, se o novo caminho dylib for mais longo que o anterior, haverá espaço para ele.
Em segundo lugar, use o install_name_tool para alterar cada caminho dylib.
Como um exemplo prático, digamos que seu aplicativo usa libSDL e otool lista sua localização como "/opt/local/lib/libSDL-1.2.0.dylib".
Primeiro, copie-o para o pacote de aplicativos.
Em seguida, edite o executável para usar o novo local (NOTA: certifique-se de criá-lo com o sinalizador -headerpad_max_install_names)
Ufa, estamos quase terminando. Agora há um pequeno problema com o diretório de trabalho atual.
Ao iniciar seu aplicativo, o diretório atual será o diretório acima de onde o aplicativo está localizado. Por exemplo: Se você colocar o foo.app na pasta / Applcations, o diretório atual ao iniciar o aplicativo será a pasta / Applications. Não o /Applications/foo.app/Contents/MacOS/ como você poderia esperar.
Você pode alterar seu aplicativo para dar conta disso, ou pode usar este pequeno script de iniciador mágico que mudará o diretório atual e iniciará seu aplicativo.
Certifique-se de ajustar o arquivo Info.plist de forma que CFBundleExecutable aponte para o script de inicialização e não para o executável anterior.
Ok, tudo feito agora. Felizmente, depois de saber todas essas coisas, você as enterra em um script de construção.
fonte
Na verdade, encontrei uma ferramenta muito útil que merece algum crédito ... NÃO - eu não desenvolvi isso;)
https://github.com/auriamg/macdylibbundler/
Ele resolverá todas as dependências e "consertará" seu executável, bem como seus arquivos dylib para funcionarem perfeitamente em seu pacote de aplicativos.
... também verificará as dependências de suas bibliotecas dinâmicas dependentes: D
fonte
Eu uso isso no meu Makefile ... Ele cria um pacote de aplicativos. Leia e entenda, porque você precisará de um arquivo de ícone png em uma pasta / macosx junto com os arquivos PkgInfo e Info.plist que incluo aqui ...
"funciona no meu computador" ... Eu uso isso para vários aplicativos no Mavericks ...
APPNAME=MyApp APPBUNDLE=$(APPNAME).app APPBUNDLECONTENTS=$(APPBUNDLE)/Contents APPBUNDLEEXE=$(APPBUNDLECONTENTS)/MacOS APPBUNDLERESOURCES=$(APPBUNDLECONTENTS)/Resources APPBUNDLEICON=$(APPBUNDLECONTENTS)/Resources appbundle: macosx/$(APPNAME).icns rm -rf $(APPBUNDLE) mkdir $(APPBUNDLE) mkdir $(APPBUNDLE)/Contents mkdir $(APPBUNDLE)/Contents/MacOS mkdir $(APPBUNDLE)/Contents/Resources cp macosx/Info.plist $(APPBUNDLECONTENTS)/ cp macosx/PkgInfo $(APPBUNDLECONTENTS)/ cp macosx/$(APPNAME).icns $(APPBUNDLEICON)/ cp $(OUTFILE) $(APPBUNDLEEXE)/$(APPNAME) macosx/$(APPNAME).icns: macosx/$(APPNAME)Icon.png rm -rf macosx/$(APPNAME).iconset mkdir macosx/$(APPNAME).iconset sips -z 16 16 macosx/$(APPNAME)Icon.png --out macosx/$(APPNAME).iconset/icon_16x16.png sips -z 32 32 macosx/$(APPNAME)Icon.png --out macosx/$(APPNAME).iconset/icon_16x16@2x.png sips -z 32 32 macosx/$(APPNAME)Icon.png --out macosx/$(APPNAME).iconset/icon_32x32.png sips -z 64 64 macosx/$(APPNAME)Icon.png --out macosx/$(APPNAME).iconset/icon_32x32@2x.png sips -z 128 128 macosx/$(APPNAME)Icon.png --out macosx/$(APPNAME).iconset/icon_128x128.png sips -z 256 256 macosx/$(APPNAME)Icon.png --out macosx/$(APPNAME).iconset/icon_128x128@2x.png sips -z 256 256 macosx/$(APPNAME)Icon.png --out macosx/$(APPNAME).iconset/icon_256x256.png sips -z 512 512 macosx/$(APPNAME)Icon.png --out macosx/$(APPNAME).iconset/icon_256x256@2x.png sips -z 512 512 macosx/$(APPNAME)Icon.png --out macosx/$(APPNAME).iconset/icon_512x512.png cp macosx/$(APPNAME)Icon.png macosx/$(APPNAME).iconset/icon_512x512@2x.png iconutil -c icns -o macosx/$(APPNAME).icns macosx/$(APPNAME).iconset rm -r macosx/$(APPNAME).iconset
Info.plist
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>CFBundleDevelopmentRegion</key> <string>English</string> <key>CFBundleExecutable</key> <string>MyApp</string> <key>CFBundleGetInfoString</key> <string>0.48.2, Copyright 2013 my company</string> <key>CFBundleIconFile</key> <string>MyApp.icns</string> <key>CFBundleIdentifier</key> <string>com.mycompany.MyApp</string> <key>CFBundleDocumentTypes</key> <array> </array> <key>CFBundleInfoDictionaryVersion</key> <string>6.0</string> <key>CFBundlePackageType</key> <string>APPL</string> <key>CFBundleShortVersionString</key> <string>0.48.2</string> <key>CFBundleSignature</key> <string>MyAp</string> <key>CFBundleVersion</key> <string>0.48.2</string> <key>NSHumanReadableCopyright</key> <string>Copyright 2013 my company.</string> <key>LSMinimumSystemVersion</key> <string>10.3</string> </dict> </plist>
PkgInfo
fonte
A solução mais simples é: crie uma vez um projeto Xcode sem alterar nada (ou seja, mantenha o aplicativo simples de uma janela que o Xcode cria para você), construa-o e copie o pacote que ele criou para você. Em seguida, edite os arquivos (notavelmente o Info.plist) para se adequar ao seu conteúdo e coloque seu próprio binário no diretório Contents / MacOS /.
fonte
Existem algumas ferramentas de código aberto para ajudar a construir pacotes de aplicativos com bibliotecas dependentes para ambientes específicos, por exemplo, py2app para aplicativos baseados em Python. Se você não encontrar um mais geral, talvez possa adaptá-lo às suas necessidades.
fonte
Eu gostaria de ter encontrado este post antes ....
Esta é minha maneira superficial de resolver esse problema usando uma
Run script
fase que é invocada toda vez que eu crio umaRelease
versão do meu aplicativo:# this is an array of my dependencies' libraries paths # which will be iterated in order to find those dependencies using otool -L libpaths=("$NDNRTC_LIB_PATH" "$BOOST_LIB_PATH" "$NDNCHAT_LIB_PATH" "$NDNCPP_LIB_PATH" "/opt/local/lib") frameworksDir=$BUILT_PRODUCTS_DIR/$FRAMEWORKS_FOLDER_PATH executable=$BUILT_PRODUCTS_DIR/$EXECUTABLE_PATH #echo "libpaths $libpaths" bRecursion=0 lRecursion=0 # this function iterates through libpaths array # and checks binary with "otool -L" command for containment # of dependency which has "libpath" path # if such dependency has been found, it will be copied to Frameworks # folder and binary will be fixed with "install_name_tool -change" command # to point to Frameworks/<dependency> library # then, dependency is checked recursively with resolveDependencies function function resolveDependencies() { local binfile=$1 local prefix=$2 local binname=$(basename $binfile) local offset=$((lRecursion*20)) printf "%s :\t%s\n" $prefix "resolving $binname..." for path in ${libpaths[@]}; do local temp=$path #echo "check lib path $path" local pattern="$path/([A-z0-9.-]+\.dylib)" while [[ "$(otool -L ${binfile})" =~ $pattern ]]; do local libname=${BASH_REMATCH[1]} otool -L ${binfile} #echo "found match $libname" printf "%s :\t%s\n" $prefix "fixing $libname..." local libpath="${path}/$libname" #echo "cp $libpath $frameworksDir" ${SRCROOT}/sudocp.sh $libpath $frameworksDir/$libname $(whoami) local installLibPath="@rpath/$libname" #echo "install_name_tool -change $libpath $installLibPath $binfile" if [ "$libname" == "$binname" ]; then install_name_tool -id "@rpath/$libname" $binfile printf "%s :\t%s\n" $prefix "fixed id for $libname." else install_name_tool -change $libpath $installLibPath $binfile printf "%s :\t%s\n" $prefix "$libname dependency resolved." let lRecursion++ resolveDependencies "$frameworksDir/$libname" "$prefix>$libname" resolveBoostDependencies "$frameworksDir/$libname" "$prefix>$libname" let lRecursion-- fi path=$temp done # while done # for printf "%s :\t%s\n" $prefix "$(basename $binfile) resolved." } # resolveDependencies # for some reason, unlike other dependencies which maintain full path # in "otool -L" output, boost libraries do not - they just appear # as "libboost_xxxx.dylib" entries, without fully qualified path # thus, resolveDependencies can't be used and a designated function is needed # this function works pretty much in a similar way to resolveDependencies # but targets only dependencies starting with "libboost_", copies them # to the Frameworks folder and resolves them recursively function resolveBoostDependencies() { local binfile=$1 local prefix=$2 local binname=$(basename $binfile) local offset=$(((bRecursion+lRecursion)*20)) printf "%s :\t%s\n" $prefix "resolving Boost for $(basename $binfile)..." local pattern="[[:space:]]libboost_([A-z0-9.-]+\.dylib)" while [[ "$(otool -L ${binfile})" =~ $pattern ]]; do local libname="libboost_${BASH_REMATCH[1]}" #echo "found match $libname" local libpath="${BOOST_LIB_PATH}/$libname" #echo "cp $libpath $frameworksDir" ${SRCROOT}/sudocp.sh $libpath $frameworksDir/$libname $(whoami) installLibPath="@rpath/$libname" #echo "install_name_tool -change $libname $installLibPath $binfile" if [ "$libname" == "$binname" ]; then install_name_tool -id "@rpath/$libname" $binfile printf "%s :\t%s\n" $prefix "fixed id for $libname." else install_name_tool -change $libname $installLibPath $binfile printf "%s :\t%s\n" $prefix "$libname Boost dependency resolved." let bRecursion++ resolveBoostDependencies "$frameworksDir/$libname" "$prefix>$libname" let bRecursion-- fi done # while printf "%s :\t%s\n" $prefix "$(basename $binfile) resolved." } resolveDependencies $executable $(basename $executable) resolveBoostDependencies $executable $(basename $executable)
Espero que isso possa ser útil para alguém.
fonte
Uma solução alternativa para que o menu funcione no Mac com o código wxWidget é simplesmente:
Concordo que um pacote de aplicativos é a maneira correta de construir um programa em um Mac. Esta é apenas uma solução simples para obter ajuda durante a depuração.
Editar: no Mac Catalina, wxWidgets 3.1.4, com g ++ 4.2.1 (novembro de 2020)
fonte