Pergunta

Suponha que eu fiz um aplicativo um OSX sem usar o Xcode. Depois de compilar com o GCC, recebo um executável vinculado a várias outras bibliotecas. Algumas dessas bibliotecas podem estar novamente ligadas dinamicamente a outras bibliotecas de sistemas não padrão

Existe alguma ferramenta que exista que faça um pacote de aplicativos OSX, primeiro produzindo as estruturas de diretório necessárias e, em seguida, copiando/verificando recorrentes e verificando links para garantir que todas as dependências dinâmicas também estejam no pacote de aplicativos?

Acho que posso tentar escrever algo assim, mas queria saber se algo assim já existe.

Foi útil?

Solução

Existem duas maneiras de criar um pacote de aplicativos no Macosx, o Easy e o Feio.

A maneira mais fácil é apenas usar o Xcode. Feito.

O problema às vezes você não pode.

No meu caso, estou construindo um aplicativo que cria outros aplicativos. Não posso presumir que o usuário tenha o Xcode instalado. Eu também estou usando Macports Para construir as bibliotecas, meu aplicativo depende. Preciso garantir que esses Dylibs sejam agrupados com o aplicativo antes de distribuí -lo.

Isenção de responsabilidade: Não sou totalmente qualificado para escrever este post, tudo foi reluzente do Apple Docs, escolhendo aplicativos existentes e tentativa e erro. Funciona para mim, mas provavelmente está errado. Envie -me 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 hipotético.

foo.app/
    Contents/
        Info.plist
        MacOS/
            foo
        Resources/
            foo.icns

Info.plist é um arquivo XML simples. Você pode editá -lo com um editor de texto ou o aplicativo Editor da Lista de Propriedades que vem com o Xcode. (Está em/desenvolvedor/aplicações/utilitários/diretório).

As principais coisas que você precisa incluir são:

CfbundleName - O nome do aplicativo.

CFBNDLDERICON - Um arquivo de ícone assumido como em conteúdo/recursos dir. Use o aplicativo Icon Composer para criar o ícone. (Ele também está no/desenvolvedor/aplicativos/utilitários/diretório) Você pode apenas arrastar e soltar um PNG para a janela e deve gerar automaticamente os níveis MIP para você.

Cfbundlexecutable - Nome do arquivo executável assumido como conteúdo/ macOS/ sub-foldador.

Existem muito mais opções, as listadas acima são apenas o mínimo. Aqui está alguma documentação da Apple sobre oInfo.plist arquivo eEstrutura do pacote de aplicativos.

Além disso, aqui está uma amostra 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>CFBundleGetInfoString</key>
  <string>Foo</string>
  <key>CFBundleExecutable</key>
  <string>foo</string>
  <key>CFBundleIdentifier</key>
  <string>com.your-company-name.www</string>
  <key>CFBundleName</key>
  <string>foo</string>
  <key>CFBundleIconFile</key>
  <string>foo.icns</string>
  <key>CFBundleShortVersionString</key>
  <string>0.01</string>
  <key>CFBundleInfoDictionaryVersion</key>
  <string>6.0</string>
  <key>CFBundlePackageType</key>
  <string>APPL</string>
  <key>IFMajorVersion</key>
  <integer>0</integer>
  <key>IFMinorVersion</key>
  <integer>1</integer>
</dict>
</plist>

Em um mundo perfeito, você pode simplesmente colocar seu executável no conteúdo/ macOS/ dir e ser feito. No entanto, se o seu aplicativo tiver alguma dependência não padrão do Dylib, ele não funcionará. Como o Windows, o macOS vem com seu próprio tipo especial de Dll Inferno.

Se você está usando Macports Para criar bibliotecas às quais você vincular, os locais dos Dylibs serão codificados no seu executável. Se você executar o aplicativo em uma máquina que possui os Dylibs no mesmo local, ele funcionará bem. No entanto, a maioria dos usuários não os possui; Quando clique duas vezes em seu aplicativo, ele simplesmente trava.

Antes de distribuir seu executável, você precisará coletar todos os Dylibs que ele carrega e copiá -los no pacote de aplicativos. Você também precisará editar o executável para que ele procure os Dylibs no local correto. ou seja, onde você os copiou.

A edição manual de um executável parece perigoso, certo? Felizmente, existem ferramentas de linha de comando para ajudar.

otool -L executable_name

Este comando listará todos os Dylibs dos quais seu aplicativo depende. Se você vir algum que não esteja no sistema/biblioteca ou na pasta USR/LIB, esses são os que você precisará copiar no pacote de aplicativos. Copie -os no/contents/macOS/pasta. Em seguida, você precisará editar o executável para usar os novos Dylibs.

Primeiro, você precisa garantir que o link usando o sinalizador -headerpad_max_install_names. Isso apenas garante que, se o novo caminho de Dylib for mais longo que o anterior, haverá espaço para isso.

Segundo, use o install_name_tool para alterar cada caminho Dylib.

install_name_tool -change existing_path_to_dylib @executable_path/blah.dylib executable_name

Como exemplo prático, digamos que seu aplicativo use Libsdl, e otool lista sua localização como "/opt/local/lib/libsdl-1.2.0.dylib".

Primeiro, copie -o no pacote de aplicativos.

cp /opt/local/lib/libSDL-1.2.0.dylib foo.app/Contents/MacOS/

Em seguida, edite o executável para usar o novo local (Nota: Certifique -se de construí -lo com o sinalizador -headerpad_max_install_names)

install_name_tool -change /opt/local/lib/libSDL-1.2.0.dylib @executable_path/libSDL-1.2.0.dylib foo.app/Contents/MacOS/foo

Ufa, estamos quase terminando. Agora, há um pequeno problema no diretório de trabalho atual.

Quando você iniciar seu aplicativo, o diretório atual será o diretório acima, onde o aplicativo estiver localizado. Por exemplo: se você colocar o Foo.app na pasta /ApplCations, o diretório atual quando você iniciar o aplicativo será a pasta /Applications. Não os /pplications/foo.app/contents/macos/, como você pode esperar.

Você pode alterar seu aplicativo para dar conta disso ou pode usar este script Magic Little Launcher que alterará o diretório atual e iniciará seu aplicativo.

#!/bin/bash
cd "${0%/*}"
./foo

Certifique -se de ajustar o arquivo info.plist para que Cfbundlexecutable aponta para o script de lançamento e não para o executável anterior.

Ok, tudo feito agora. Felizmente, depois que você conhece todas essas coisas que você enterra em um script de construção.

Outras dicas

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 "corrigirá" o executável e os arquivos Dylib para funcionar sem problemas no pacote de aplicativos.

... também verificará as dependências do seu Libs dinâmico dependente: D

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ê), crie-o e copie o pacote que criou para você. Em seguida, edite os arquivos (principalmente os info.plist) para se adequar ao seu conteúdo e coloque seu próprio binário no conteúdo/ MacOS/ diretório.

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

APPLMyAp

Existem algumas ferramentas de código aberto para ajudar a criar pacotes de aplicativos com bibliotecas dependentes para ambientes específicos, por exemplo, py2app Para aplicações baseadas em Python. Se você não encontrar um mais geral, talvez possa adaptá -lo às suas necessidades.

Eu gostaria de ter encontrado este post anteriormente ....

Aqui está minha maneira superficial de resolver esse problema usando um Run script fase que é invocada toda vez que construo um Release 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.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top