Membangun OSX App Bundle


90

Misalkan saya telah membuat aplikasi osX tanpa menggunakan Xcode. Setelah dikompilasi dengan GCC, saya mendapatkan file yang dapat dieksekusi yang ditautkan ke beberapa perpustakaan lain. Beberapa pustaka tersebut mungkin lagi ditautkan secara dinamis ke pustaka sistem non-standar lainnya

Apakah ada alat yang ada yang membuat OSX App bundle dengan terlebih dahulu membuat struktur direktori yang diperlukan dan kemudian secara rekursif menyalin / memeriksa / memperbaiki link untuk memastikan semua dependensi dinamis juga ada dalam app bundle?

Saya kira saya dapat mencoba menulis sesuatu seperti ini tetapi saya bertanya-tanya apakah sesuatu seperti ini sudah ada.


4
Saya tidak dapat menggunakan Xcode karena beberapa alasan. salah satunya karena saya menggunakan custom gcc. Xcode tidak mengizinkan saya menentukan gcc yang berbeda. Saya menggunakan cmake untuk membuat makefile saya.
Yogi

>> Beberapa dari perpustakaan tersebut mungkin lagi secara dinamis ditautkan ke perpustakaan sistem non-standar lainnya << Respon yang dipilih tidak membantu dalam kasus ini. Bagaimana Anda mengatasinya?
FlowUI. SimpleUITesting.com

Jawaban:


143

Ada dua cara untuk membuat bundel aplikasi di MacOSX, Mudah dan Jelek.

Cara termudah adalah dengan menggunakan Xcode. Selesai.

Masalahnya terkadang Anda tidak bisa.

Dalam kasus saya, saya sedang membangun aplikasi yang membangun aplikasi lain. Saya tidak dapat berasumsi bahwa pengguna telah menginstal XCode. Saya juga menggunakan MacPorts untuk membangun perpustakaan tempat aplikasi saya bergantung. Saya perlu memastikan bahwa dylib ini digabungkan dengan aplikasi sebelum saya mendistribusikannya.

Penafian: Saya benar-benar tidak memenuhi syarat untuk menulis posting ini, semua yang ada di dalamnya telah dikilaukan dari dokumen Apple, memilah-milah aplikasi yang ada dan coba-coba. Ini berhasil untuk saya, tetapi kemungkinan besar salah. Silakan email saya jika Anda memiliki koreksi.

Hal pertama yang harus Anda ketahui adalah bahwa app bundle hanyalah sebuah direktori.
Mari kita periksa struktur hipotetis foo.app.

foo.app/
    Isi/
        Info.plist
        MacOS /
            foo
        Sumber daya /
            foo.icns

Info.plist adalah file XML biasa. Anda dapat mengeditnya dengan editor teks atau aplikasi Editor Daftar Properti yang dibundel dengan XCode. (Ada di / Developer / Applications / Utilities / direktori).

Hal-hal utama yang perlu Anda sertakan adalah:

CFBundleName - Nama aplikasi.

CFBundleIcon - File Ikon yang diasumsikan ada di direktori Contents / Resources. Gunakan aplikasi Icon Composer untuk membuat ikon. (Ini juga ada di / Developer / Applications / Utilities / direktori) Anda cukup menarik dan melepas png ke jendelanya dan secara otomatis akan menghasilkan mip-level untuk Anda.

CFBundleExecutable - Nama file yang dapat dieksekusi yang diasumsikan berada di sub-folder Contents / MacOS /.

Ada lebih banyak pilihan, yang tercantum di atas hanyalah minimum. Berikut beberapa dokumentasi Apple tentang file Info.plist dan struktur App bundle .

Juga, Ini contoh 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">
<dikt>
  <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>

Di dunia yang sempurna Anda bisa memasukkan file yang dapat dieksekusi ke dalam Contents / MacOS / dir dan selesai. Namun, jika aplikasi Anda memiliki dependensi dylib non-standar, itu tidak akan berfungsi. Seperti Windows, MacOS hadir dengan DLL Hell jenis khusus itu sendiri .

Jika Anda menggunakan MacPorts untuk membangun pustaka yang Anda tautkan, lokasi dylib akan di-hardcode ke dalam file yang dapat dieksekusi. Jika Anda menjalankan aplikasi pada mesin yang dylibnya berada di lokasi yang sama persis, aplikasi akan berjalan dengan baik. Namun, sebagian besar pengguna tidak akan menginstalnya; ketika mereka mengklik dua kali aplikasi Anda, itu hanya akan mogok.

Sebelum mendistribusikan file executable, Anda harus mengumpulkan semua dylib yang dimuatnya dan menyalinnya ke app bundle. Anda juga perlu mengedit file yang dapat dieksekusi agar itu akan mencari dylib di tempat yang benar. yaitu tempat Anda menyalinnya.

Mengedit tangan yang dapat dieksekusi terdengar berbahaya bukan? Untungnya ada alat baris perintah untuk membantu.

otool -L nama_eksekusi

Perintah ini akan mencantumkan semua dylib yang bergantung pada aplikasi Anda. Jika Anda melihat ada yang TIDAK ada di folder System / Library atau usr / lib, itu adalah yang harus Anda salin ke app bundle. Salin ke folder / Contents / MacOS /. Selanjutnya, Anda harus mengedit file yang dapat dieksekusi untuk menggunakan dylib baru.

Pertama, Anda perlu memastikan bahwa Anda menautkan menggunakan tanda -headerpad_max_install_names. Ini hanya memastikan bahwa jika jalur dylib baru lebih panjang dari yang sebelumnya, akan ada ruang untuk itu.

Kedua, gunakan install_name_tool untuk mengubah setiap jalur dylib.

install_name_tool -ubah existing_path_to_dylib @ executable_path / blah.dylib executable_name

Sebagai contoh praktis, Misalkan aplikasi Anda menggunakan libSDL , dan otool mencantumkan lokasinya sebagai "/opt/local/lib/libSDL-1.2.0.dylib".

Salin dulu ke app bundle.

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

Kemudian edit file yang dapat dieksekusi untuk menggunakan lokasi baru (CATATAN: pastikan Anda membuatnya dengan flag -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

Wah, kita hampir selesai. Sekarang ada masalah kecil dengan direktori kerja saat ini.

Saat Anda memulai aplikasi, direktori saat ini adalah direktori di atas tempat aplikasi berada. Misalnya: Jika Anda menempatkan foo.app di folder / Applcations, maka direktori saat ini saat Anda meluncurkan aplikasi adalah folder / Applications. Bukan /Applications/foo.app/Contents/MacOS/ seperti yang Anda harapkan.

Anda dapat mengubah aplikasi Anda ke akun untuk ini, atau Anda dapat menggunakan skrip peluncur kecil ajaib ini yang akan mengubah direktori saat ini dan meluncurkan aplikasi Anda.

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

Pastikan Anda menyesuaikan file Info.plist sehingga CFBundleExecutable menunjuk ke skrip peluncuran dan bukan ke eksekusi sebelumnya.

Oke, sudah selesai sekarang. Untungnya, setelah Anda mengetahui semua hal ini, Anda menguburnya dalam skrip build.


7
+1. Namun, anjing zombie itu membuatku takut. Bisakah Anda menggunakan gambar yang tidak terlalu menyakitkan untuk menggambarkan neraka DLL? (Belum lagi istilah "neraka DLL" tidak berlaku. Metode yang disukai untuk mendistribusikan perpustakaan dinamis adalah melalui kerangka kerja yang menghindari sebagian besar masalah. Namun, pendekatan .dylib berguna untuk perpustakaan gaya UNIX.)
Heinrich Apfelmus

1
Bagaimana cara kami memperbaiki ketergantungan ganda? Seperti seorang dylib merujuk pada dylib lain?
FlowUI. SimpleUITesting.com

Apakah tidak ada cara untuk mengkompilasi file yang dapat dieksekusi dengan lokasi yang benar sehingga Anda tidak perlu mengeditnya?
sinkronisasi

Apakah ini akan berfungsi lagi di Catalina, mengingat betapa ketatnya keamanannya? (memodifikasi dylib, binari, dll.)
sinkronisasi

20

Saya benar-benar menemukan alat yang sangat berguna yang pantas mendapatkan pujian ... TIDAK - saya tidak mengembangkan ini;)

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

Ini akan menyelesaikan semua dependensi dan "memperbaiki" file executable serta dylib Anda agar berfungsi dengan lancar di app bundle Anda.

... itu juga akan memeriksa dependensi libs dinamis dependen Anda: D


ini alat yang sangat berguna! Terima kasih telah berbagi dan mengembangkannya.
xpnimi

Di sini tidak ada dependensi dependensi. Memperbaiki?
Peter

9

Saya menggunakan ini di Makefile saya ... Ini membuat bundel aplikasi. Baca dan pahami, karena Anda memerlukan file ikon png di folder macosx / bersama dengan file PkgInfo dan Info.plist yang saya sertakan di sini ...

"ini berfungsi di komputer saya" ... Saya menggunakan ini untuk beberapa aplikasi di 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

Anda dapat membuat Info.plist memiliki placeholder teks dan kemudian menggunakan sesuatu seperti sed atau awk untuk membuat Info.plist terakhir dengan bidang yang benar. Atau bahkan mungkin menggunakan plutil untuk membuat plist.
Mark Heath

8

Solusi paling sederhana adalah: buat sekali proyek Xcode tanpa mengubah apa pun (yaitu, simpan aplikasi satu jendela sederhana yang dibuat Xcode untuk Anda), buat, dan salin bundel yang dibuatnya untuk Anda. Kemudian, edit file (terutama Info.plist) agar sesuai dengan konten Anda, dan letakkan biner Anda sendiri di direktori Contents / MacOS /.


4
Pertanyaannya secara eksplisit menanyakan bagaimana melakukannya tanpa xcode. Saya di perahu yang sama dan ingin jawaban yang nyata.
hyperlogic

5
Nah, dengan ini, Anda hanya perlu menggunakan Xcode sekali dalam hidup Anda :) atau meminta seseorang untuk melakukannya untuk Anda.
F'x

@ F'x Bisakah Anda menerbitkan .app yang dihasilkan sebagai contoh di suatu tempat?
endolith

3

Ada beberapa alat open source untuk membantu membangun app bundle dengan pustaka dependen untuk lingkungan tertentu, misalnya, py2app untuk aplikasi berbasis Python. Jika Anda tidak menemukan yang lebih umum, mungkin Anda dapat menyesuaikannya dengan kebutuhan Anda.


2

Saya berharap saya telah menemukan posting ini sebelumnya ....

Inilah cara samar saya untuk menyelesaikan masalah ini menggunakan Run scriptfase yang dipanggil setiap kali saya membuat Releaseversi aplikasi saya:

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

Semoga ini bisa bermanfaat bagi seseorang.


0

Solusi agar menu berfungsi di Mac dengan kode wxWidget adalah dengan:

  1. Mulai program di terminal seperti biasa (./appname)
  2. GUI program dimulai seperti biasa, dan klik terminal agar aplikasi kehilangan fokus
  3. Klik GUI untuk mendapatkan kembali fokus dan item menu berfungsi.

Saya setuju bahwa app bundle adalah cara yang benar untuk membuat program di Mac. Ini hanyalah solusi sederhana untuk bantuan selama debugging.

Sunting: Ini di Mac Catalina, wxWidgets 3.1.4, dengan g ++ 4.2.1 (Nov 2020)

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.