OSX Uygulama Paketi Oluşturma


90

Xcode kullanmadan bir osX uygulaması yaptığımı varsayalım. GCC ile derledikten sonra, diğer birçok kütüphaneye bağlı bir yürütülebilir dosya elde ediyorum. Bu kitaplıklardan bazıları, diğer standart olmayan sistem kitaplıklarına da dinamik olarak bağlanabilir.

Tüm dinamik bağımlılıkların uygulama paketinde de olduğundan emin olmak için önce gerekli dizin yapılarını oluşturarak ve ardından bağlantıları tekrar tekrar kopyalayarak / kontrol ederek / düzelterek bir OSX Uygulama paketi oluşturan herhangi bir araç var mı?

Sanırım böyle bir şey yazmayı deneyebilirim ama böyle bir şeyin zaten var olup olmadığını merak ediyordum.


4
Xcode'u birkaç nedenden dolayı kullanamıyorum. bunlardan biri özel gcc kullanmam. Xcode farklı bir gcc belirlememe izin vermiyor. Makefile dosyalarımı oluşturmak için cmake kullanıyorum.
Yogi

>> Bu kitaplıklardan bazıları diğer standart olmayan sistem kitaplıklarına da dinamik olarak bağlanabilir << Seçilen yanıt bu durumda yardımcı olmuyor. Nasıl çözdün?
Just a coder

Yanıtlar:


144

MacOSX'te bir uygulama paketi oluşturmanın iki yolu vardır: Easy ve Ugly.

Kolay yol sadece XCode'u kullanmaktır. Bitti.

Sorun, bazen yapamazsınız.

Benim durumumda, diğer uygulamaları oluşturan bir uygulama geliştiriyorum. Kullanıcının XCode yüklü olduğunu varsayamıyorum. Ayrıca uygulamamın bağlı olduğu kitaplıkları oluşturmak için MacPorts kullanıyorum . Dağıtmadan önce bu dylib'lerin uygulama ile birlikte paketlendiğinden emin olmam gerekiyor.

Sorumluluk reddi: Bu yazıyı yazmak için tamamen vasıfsızım, içindeki her şey Apple belgelerinden parıldadı, mevcut uygulamaları ve deneme yanılma yöntemlerini birbirinden ayırdı. Benim için işe yarıyor, ancak büyük olasılıkla yanlış. Herhangi bir düzeltmeniz varsa lütfen bana e-posta gönderin.

Bilmeniz gereken ilk şey, uygulama paketinin sadece bir dizin olduğudur.
Varsayımsal bir foo.app yapısını inceleyelim.

foo.app/
    İçindekiler /
        Info.plist
        Mac os işletim sistemi/
            foo
        Kaynaklar /
            foo.icns

Info.plist düz bir XML dosyasıdır. Bunu bir metin düzenleyici veya XCode ile birlikte gelen Özellik Listesi Düzenleyici uygulamasıyla düzenleyebilirsiniz. (/ Developer / Applications / Utilities / dizinindedir).

Dahil etmeniz gereken temel şeyler şunlardır:

CFBundleName - Uygulamanın adı.

CFBundleIcon - İçindekiler / Kaynaklar dizininde olduğu varsayılan bir Simge dosyası. Simgeyi oluşturmak için Icon Composer uygulamasını kullanın. (Ayrıca / Developer / Applications / Utilities / dizinindedir) Bir png'yi penceresine sürükleyip bırakabilirsiniz ve sizin için otomatik olarak mip düzeylerini oluşturmalısınız.

CFBundleExecutable - İçerik / MacOS / alt klasörde olduğu varsayılan yürütülebilir dosyanın adı.

Çok daha fazla seçenek var, yukarıda listelenenler yalnızca minimum olanlardır. Info.plist dosyası ve Uygulama paketi yapısıyla ilgili bazı Apple belgeleri burada .

Ayrıca, örnek bir Info.plist burada verilmiştir.

<? 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-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> UYGULAMA </string>
  <key> IFMajorVersion </key>
  <integer> 0 </integer>
  <key> IFMinorVersion </key>
  <integer> 1 </integer>
</dict>
</plist>

Mükemmel bir dünyada, çalıştırılabilir dosyanızı İçerik / MacOS / dizinine bırakabilir ve işiniz bitebilir. Ancak, uygulamanızın standart olmayan herhangi bir dylib bağımlılığı varsa çalışmaz. Windows gibi, MacOS de kendine özgü bir DLL Cehennemi ile birlikte gelir .

Bağlandığınız kitaplıkları oluşturmak için MacPorts kullanıyorsanız , dylib'lerin konumları çalıştırılabilir dosyanıza sabit kodlanacaktır. Uygulamayı tam olarak aynı konumda olan bir makinede çalıştırırsanız, sorunsuz çalışır. Ancak çoğu kullanıcı bunları yüklemeyecektir; uygulamanızı çift tıkladıklarında, yalnızca çökecektir.

Çalıştırılabilir dosyayı dağıtmadan önce, yüklediği tüm dylib'leri toplamanız ve bunları uygulama paketine kopyalamanız gerekir. Ayrıca yürütülebilir dosyayı, dylib'leri doğru yerde arayacak şekilde düzenlemeniz gerekecektir. yani bunları nereye kopyaladığınız.

Çalıştırılabilir bir dosyayı elle düzenlemek tehlikeli geliyor, değil mi? Neyse ki yardımcı olacak komut satırı araçları var.

otool -L çalıştırılabilir_adı

Bu komut, uygulamanızın bağlı olduğu tüm dylib'leri listeleyecektir. System / Library veya usr / lib klasöründe OLMAYAN herhangi birini görürseniz, bunlar uygulama paketine kopyalamanız gerekenlerdir. Bunları / Contents / MacOS / klasörüne kopyalayın. Ardından, yeni dylib'leri kullanmak için yürütülebilir dosyayı düzenlemeniz gerekir.

Öncelikle, -headerpad_max_install_names bayrağını kullanarak bağladığınızdan emin olmanız gerekir. Bu sadece yeni dylib yolu öncekinden daha uzunsa, ona yer açılmasını sağlar.

İkinci olarak, her dylib yolunu değiştirmek için install_name_tool kullanın.

install_name_tool - mevcut_path_to_dylib @ execable_path / blah.dylib yürütülebilir_adı değiştirin

Pratik bir örnek olarak, uygulamanızın libSDL kullandığını ve otool'un konumunu "/opt/local/lib/libSDL-1.2.0.dylib" olarak listelediğini varsayalım.

Önce bunu uygulama paketine kopyalayın.

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

Ardından yeni konumu kullanmak için yürütülebilir dosyayı düzenleyin (NOT: -headerpad_max_install_names bayrağıyla oluşturduğunuzdan emin olun)

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

Whew, neredeyse bitti. Şimdi mevcut çalışma diziniyle ilgili küçük bir sorun var.

Uygulamanızı başlattığınızda mevcut dizin, uygulamanın bulunduğu yerin yukarısındaki dizin olacaktır. Örneğin: foo.app dosyasını / Applcations klasörüne yerleştirirseniz, uygulamayı başlattığınızda mevcut dizin / Applications klasörü olacaktır. Beklediğiniz gibi /Applications/foo.app/Contents/MacOS/ değil.

Bunu hesaba katmak için uygulamanızı değiştirebilir veya mevcut dizini değiştirecek ve uygulamanızı başlatacak bu sihirli küçük başlatıcı komut dosyasını kullanabilirsiniz.

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

Info.plist dosyasını CFBundleExecutable'ın önceki çalıştırılabilir dosyayı değil başlatma komut dosyasını gösterecek şekilde ayarladığınızdan emin olun .

Tamam, şimdi hepsi bitti. Neyse ki, tüm bunları öğrendikten sonra onu bir yapı betiğine gömüyorsunuz.


7
+1. Ancak, zombi köpek beni korkutuyor. DLL cehennemini göstermek için daha az yara izi bırakan bir resim kullanabilir misin? ("DLL cehennemi" teriminin geçerli olmadığından bahsetmiyorum bile. Dinamik kitaplıkları dağıtmak için tercih edilen yöntem, çoğu sorunu önleyen çerçevelerdir. Yine de, .dylib yaklaşımı UNIX tarzı kitaplıklar için yararlıdır.)
Heinrich Apfelmus

1
Çift bağımlılıkları nasıl düzeltiriz? Bir dylib'in başka bir dylib'e gönderme yapması gibi mi?
Just a coder

Yürütülebilir dosyayı, düzenlemek zorunda kalmamak için doğru konumlarla derlemenin bir yolu yok mu?
senkronizeler

Güvenliğin ne kadar katı hale geldiği göz önüne alındığında, bu artık Catalina'da işe yarayacak mı? (ikili dosyaları, ikili dosyaları vb. modifiye etme)
senkronizör

20

Aslında biraz övgüyü hak eden çok kullanışlı bir araç buldum ... HAYIR - Bunu ben geliştirmedim;)

https://github.com/auriamg/macdylibbundler/

Tüm bağımlılıkları çözecek ve yürütülebilir dosyanın yanı sıra dylib dosyalarınızı uygulama paketinizde sorunsuz çalışması için "düzeltecektir".

... ayrıca bağımlı dinamik kütüphanelerinizin bağımlılıklarını da kontrol edecektir: D


bu oldukça kullanışlı bir araç! Paylaştığınız ve geliştirdiğiniz için teşekkürler.
xpnimi

Burada bağımlılıkların bağımlılıkları eksik. Düzeltilsin mi?
Peter

9

Bunu Makefile dosyamda kullanıyorum ... Bir uygulama paketi oluşturur. Okuyun ve anlayın, çünkü buraya dahil ettiğim PkgInfo ve Info.plist dosyalarıyla birlikte bir macosx / klasöründe bir png simge dosyasına ihtiyacınız olacak ...

"bilgisayarımda çalışıyor" ... Bunu Mavericks'teki birden fazla uygulama için kullanıyorum ...

Info.plist

PkgInfo


Info.plist'in metin yer tutuculara sahip olmasını sağlayabilir ve ardından doğru alanlarla son Info.plist'i oluşturmak için sed veya awk gibi bir şey kullanabilirsiniz. Veya plist'i oluşturmak için plutil bile kullanabilirsiniz.
silicontrip

8

En basit çözüm şudur: hiçbir şeyi değiştirmeden bir Xcode projesi oluşturun (yani Xcode'un sizin için oluşturduğu basit tek pencereli uygulamayı saklayın), oluşturun ve sizin için oluşturduğu paketi kopyalayın. Ardından, içeriğinize uyacak şekilde dosyaları (özellikle Info.plist'i) düzenleyin ve Contents / MacOS / dizinine kendi ikili dosyanızı yerleştirin.


4
Soru açıkça bunun xcode olmadan nasıl yapılacağını sorar. Aynı gemideyim ve gerçek bir cevap istiyorum.
hyperlogic

5
Bununla birlikte, XCode'u hayatınızda yalnızca bir kez kullanmanız gerekir :) veya birinden sizin için yapmasını istemeniz gerekir.
F'x

@ F'x Oluşturulan .app'i bir yerde örnek olarak yayınlayabilir misiniz?
endolith

3

Python tabanlı uygulamalar için py2app gibi belirli ortamlar için bağımlı kitaplıklarla uygulama paketleri oluşturmaya yardımcı olacak bazı açık kaynaklı araçlar vardır . Daha genel bir tane bulamazsanız, belki onu ihtiyaçlarınıza göre uyarlayabilirsiniz.


2

Keşke bu gönderiyi daha önce bulsaydım ...

Uygulamamın Run scriptbir Releasesürümünü her oluşturduğumda başlatılan bir aşama kullanarak bu sorunu çözmenin kabataslak yolu :

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

Umarım bu birisi için yararlı olabilir.


0

Mac'te wxWidget koduyla menünün çalışması için bir çözüm basitçe:

  1. Programı her zamanki gibi bir terminalde başlatın (./appname)
  2. GUI programı normal olarak başlar ve uygulamanın odağını kaybetmesi için terminale tıklayın
  3. Odağı yeniden kazanmak için GUI'ye tıklayın ve menü öğeleri çalışır.

Uygulama paketinin, Mac'te bir program oluşturmanın doğru yolu olduğunu kabul ediyorum. Bu, hata ayıklama sırasında yardım için basit bir çözümdür.

Düzenleme: Bu, g ++ 4.2.1 ile Mac Catalina, wxWidgets 3.1.4'tedir (Kasım 2020)

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.