การสร้าง OSX App Bundle


90

สมมติว่าฉันสร้างแอป osX โดยไม่ใช้ Xcode หลังจากคอมไพล์ด้วย GCC ฉันได้รับไฟล์ปฏิบัติการซึ่งเชื่อมโยงกับไลบรารีอื่น ๆ ไลบรารีบางส่วนอาจเชื่อมโยงแบบไดนามิกกับไลบรารีระบบอื่น ๆ ที่ไม่ได้มาตรฐาน

มีเครื่องมือใดบ้างที่สร้าง OSX App Bundle โดยสร้างโครงสร้างไดเร็กทอรีที่ต้องการก่อนจากนั้นคัดลอก / ตรวจสอบ / แก้ไขลิงก์ซ้ำเพื่อให้แน่ใจว่าการอ้างอิงแบบไดนามิกทั้งหมดอยู่ใน App Bundle ด้วย

ฉันเดาว่าฉันสามารถลองเขียนอะไรแบบนี้ได้ แต่ฉันสงสัยว่ามีอะไรแบบนี้อยู่แล้ว


4
ฉันไม่สามารถใช้ Xcode ได้เนื่องจากสาเหตุหลายประการ หนึ่งในนั้นเป็นเพราะฉันใช้ gcc ที่กำหนดเอง Xcode ไม่อนุญาตให้ฉันระบุ gcc อื่น ฉันใช้ cmake เพื่อสร้าง makefiles ของฉัน
Yogi

>> บางส่วนของไลบรารีเหล่านั้นอาจเชื่อมโยงแบบไดนามิกกับไลบรารีระบบอื่น ๆ ที่ไม่ได้มาตรฐาน << การตอบกลับที่เลือกไม่ได้ช่วยในกรณีนี้ คุณแก้ปัญหาอย่างไร
แค่คนเขียนโค้ด

คำตอบ:


144

มีสองวิธีในการสร้าง App Bundle บน MacOSX คือ Easy และ Ugly

วิธีง่ายๆก็แค่ใช้ XCode เสร็จแล้ว

ปัญหาคือบางครั้งคุณทำไม่ได้

ในกรณีของฉันฉันกำลังสร้างแอปที่สร้างแอปอื่น ๆ ฉันไม่สามารถสันนิษฐานได้ว่าผู้ใช้ติดตั้ง XCode ไว้ ฉันยังใช้MacPortsเพื่อสร้างไลบรารีที่แอปของฉันขึ้นอยู่ด้วย ฉันต้องแน่ใจว่า dylibs เหล่านี้มาพร้อมกับแอพก่อนที่จะแจกจ่าย

ข้อจำกัดความรับผิดชอบ:ฉันไม่มีคุณสมบัติที่จะเขียนโพสต์นี้โดยสิ้นเชิงทุกอย่างในนั้นได้รับการรวบรวมจากเอกสารของ Apple โดยแยกแอปที่มีอยู่ออกจากกันและการลองผิดลองถูก มันได้ผลสำหรับฉัน แต่มักจะผิด โปรดส่งอีเมลถึงฉันหากคุณมีการแก้ไขใด ๆ

สิ่งแรกที่คุณควรรู้คือ App Bundle เป็นเพียงไดเร็กทอรี
ลองตรวจสอบโครงสร้างของ foo.app สมมุติ

foo.app/
    สารบัญ /
        Info.plist
        MacOS /
            ฟู
        แหล่งข้อมูล /
            foo.icns

Info.plist เป็นไฟล์ XML ธรรมดา คุณสามารถแก้ไขได้ด้วยโปรแกรมแก้ไขข้อความหรือแอปตัวแก้ไขรายการคุณสมบัติที่มาพร้อมกับ XCode (อยู่ในไดเร็กทอรี / Developer / Applications / Utilities /)

สิ่งสำคัญที่คุณต้องมี ได้แก่ :

CFBundleName - ชื่อแอป

CFBundleIcon - ไฟล์ไอคอนที่ถือว่าอยู่ใน Contents / Resources dir ใช้แอพ Icon Composer เพื่อสร้างไอคอน (นอกจากนี้ยังอยู่ในไดเร็กทอรี / Developer / Applications / Utilities /) คุณสามารถลากและวาง png ลงในหน้าต่างและควรสร้างระดับ mip ให้โดยอัตโนมัติ

CFBundleExecutable - ชื่อของไฟล์ปฏิบัติการที่ถือว่าอยู่ใน Contents / MacOS / โฟลเดอร์ย่อย

มีตัวเลือกอื่น ๆ อีกมากมายตัวเลือกที่ระบุไว้ข้างต้นเป็นเพียงขั้นต่ำเท่านั้น นี่คือบางส่วนเอกสารแอปเปิ้ลใน Info.plist ไฟล์และ โครงสร้างกำ App

นี่คือตัวอย่าง 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>

ในโลกที่สมบูรณ์แบบคุณสามารถวางไฟล์ปฏิบัติการลงใน Contents / MacOS / dir แล้วทำ อย่างไรก็ตามหากแอปของคุณมีการอ้างอิง dylib ที่ไม่ได้มาตรฐานก็จะใช้ไม่ได้ เช่น Windows, MacOS มาพร้อมกับมันเป็นชนิดพิเศษของตัวเองของDLL นรก

หากคุณใช้MacPortsเพื่อสร้างไลบรารีที่คุณเชื่อมโยงตำแหน่งของ dylibs จะถูกเข้ารหัสไว้ในไฟล์ปฏิบัติการของคุณ หากคุณเรียกใช้แอปบนเครื่องที่มี dylib ในตำแหน่งเดียวกันแอปจะทำงานได้ดี อย่างไรก็ตามผู้ใช้ส่วนใหญ่จะไม่ได้ติดตั้ง เมื่อพวกเขาคลิกสองครั้งที่แอปของคุณมันก็จะพัง

ก่อนที่คุณจะแจกจ่ายไฟล์ปฏิบัติการคุณจะต้องรวบรวม dylib ทั้งหมดที่โหลดมาและคัดลอกลงใน App Bundle คุณจะต้องแก้ไขไฟล์ปฏิบัติการเพื่อที่จะมองหา dylibs ในตำแหน่งที่ถูกต้อง นั่นคือที่ที่คุณคัดลอกไป

การแก้ไขด้วยมือที่เรียกใช้งานได้ฟังดูอันตรายใช่ไหม โชคดีที่มีเครื่องมือบรรทัดคำสั่งที่จะช่วยได้

otool -L executable_name

คำสั่งนี้จะแสดงรายการ dylib ทั้งหมดที่แอปของคุณขึ้นอยู่ หากคุณเห็นสิ่งที่ไม่ได้อยู่ในโฟลเดอร์ System / Library หรือ usr / lib นั่นคือสิ่งที่คุณต้องคัดลอกลงใน App Bundle คัดลอกลงใน / Contents / MacOS / โฟลเดอร์ ถัดไปคุณจะต้องแก้ไขไฟล์ปฏิบัติการเพื่อใช้ dylib ใหม่

ขั้นแรกคุณต้องแน่ใจว่าคุณเชื่อมโยงโดยใช้แฟล็ก -headerpad_max_install_names แค่นี้ก็ทำให้แน่ใจได้ว่าถ้าเส้นทาง dylib ใหม่ยาวกว่าเดิมก็จะมีที่ว่างสำหรับมัน

ประการที่สองใช้ install_name_tool เพื่อเปลี่ยนเส้นทาง dylib แต่ละรายการ

install_name_tool -change existing_path_to_dylib @ executable_path / blah.dylib executable_name

ตัวอย่างที่ใช้ได้จริงสมมติว่าแอปของคุณใช้libSDLและ otool แสดงตำแหน่งเป็น "/opt/local/lib/libSDL-1.2.0.dylib"

ก่อนอื่นให้คัดลอกลงใน App Bundle

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

จากนั้นแก้ไขไฟล์ปฏิบัติการเพื่อใช้ตำแหน่งใหม่ (หมายเหตุ: ตรวจสอบให้แน่ใจว่าคุณสร้างขึ้นด้วยแฟล็ก -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

เกือบเสร็จแล้ว ขณะนี้มีปัญหาเล็กน้อยกับไดเร็กทอรีการทำงานปัจจุบัน

เมื่อคุณเริ่มแอปของคุณไดเร็กทอรีปัจจุบันจะเป็นไดเร็กทอรีด้านบนที่แอปพลิเคชันตั้งอยู่ ตัวอย่างเช่นหากคุณวาง foo.app ในโฟลเดอร์ / Applcations ไดเร็กทอรีปัจจุบันเมื่อคุณเปิดแอปจะเป็นโฟลเดอร์ / Applications ไม่ใช่ /Applications/foo.app/Contents/MacOS/ อย่างที่คุณคาดหวัง

คุณสามารถปรับเปลี่ยนแอปของคุณให้เป็นบัญชีสำหรับสิ่งนี้หรือคุณสามารถใช้สคริปต์ตัวเรียกใช้งานมหัศจรรย์นี้ที่จะเปลี่ยนไดเรกทอรีปัจจุบันและเปิดแอปของคุณ

#! / bin / ทุบตี
ซีดี "$ {0% / *}"
./foo

ตรวจสอบให้แน่ใจว่าคุณได้ปรับไฟล์ Info.plist เพื่อให้CFBundleExecutableชี้ไปที่สคริปต์เรียกใช้งานไม่ใช่ไปยังไฟล์ปฏิบัติการก่อนหน้านี้

โอเคเสร็จแล้ว โชคดีที่เมื่อคุณรู้สิ่งเหล่านี้ทั้งหมดแล้วคุณจะฝังมันไว้ในบิลด์สคริปต์


7
+1. อย่างไรก็ตามสุนัขซอมบี้ทำให้ฉันประหลาดใจ คุณสามารถใช้รูปภาพที่มีรอยแผลเป็นน้อยลงเพื่อแสดงให้เห็นถึงนรกของ DLL ได้หรือไม่? (ไม่ต้องพูดถึงคำว่า "DLL hell" ใช้ไม่ได้วิธีการที่ต้องการสำหรับการกระจายไลบรารีไดนามิกคือผ่านเฟรมเวิร์กซึ่งหลีกเลี่ยงปัญหาส่วนใหญ่อย่างไรก็ตามวิธี. dylib มีประโยชน์สำหรับไลบรารีสไตล์ UNIX)
Heinrich Apfelmus

1
เราจะแก้ไขการอ้างอิงสองครั้งได้อย่างไร? เหมือน dylib อ้างอิง dylib อื่น?
แค่คนเขียนโค้ด

ไม่มีวิธีใดในการรวบรวมไฟล์ปฏิบัติการด้วยตำแหน่งที่ถูกต้องดังนั้นคุณจึงไม่ต้องแก้ไข?
ซิงโครไนซ์

สิ่งนี้จะใช้ได้กับ Catalina อีกต่อไปหรือไม่เนื่องจากการรักษาความปลอดภัยมีความเข้มงวดเพียงใด (modfying dylibs, binaries ฯลฯ )
ซิงโครไนซ์

20

ฉันพบเครื่องมือที่มีประโยชน์มากที่สมควรได้รับเครดิต ... ไม่ - ฉันไม่ได้พัฒนาสิ่งนี้

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

มันจะแก้ไขการอ้างอิงทั้งหมดและ "แก้ไข" ไฟล์ปฏิบัติการตลอดจนไฟล์ dylib ของคุณเพื่อให้ทำงานได้อย่างราบรื่นใน App Bundle

... มันจะตรวจสอบการอ้างอิงของไดนามิก libs ที่ขึ้นอยู่กับคุณด้วย: D


นี่เป็นเครื่องมือที่มีประโยชน์มาก! ขอบคุณสำหรับการแบ่งปันและพัฒนา
xpnimi

มันขาดการอ้างอิงของการอ้างอิงที่นี่ แก้ไข?
ปีเตอร์

9

ฉันใช้สิ่งนี้ใน Makefile ของฉัน ... มันสร้าง App Bundle อ่านและทำความเข้าใจเพราะคุณจะต้องมีไฟล์ไอคอน png ใน macosx / โฟลเดอร์พร้อมกับไฟล์ PkgInfo และ Info.plist ที่ฉันรวมไว้ที่นี่ ...

"มันทำงานบนคอมพิวเตอร์ของฉัน" ... ฉันใช้สิ่งนี้กับหลาย ๆ แอพใน Mavericks ...

Info.plist

PkgInfo


คุณสามารถทำให้ Info.plist มีตัวยึดข้อความแล้วใช้บางอย่างเช่น sed หรือ awk เพื่อสร้าง Info.plist สุดท้ายด้วยฟิลด์ที่ถูกต้อง หรืออาจจะใช้พลูติลเพื่อสร้าง plist
silicontrip

8

วิธีแก้ปัญหาที่ง่ายที่สุดคือสร้างโปรเจ็กต์ Xcode ครั้งเดียวโดยไม่ต้องเปลี่ยนแปลงอะไรเลย (เช่นเก็บแอพแบบหน้าต่างเดียวที่ Xcode สร้างให้คุณ) สร้างและคัดลอกบันเดิลที่สร้างไว้ จากนั้นแก้ไขไฟล์ (โดยเฉพาะอย่างยิ่ง Info.plist) เพื่อให้เหมาะกับเนื้อหาของคุณและใส่ไบนารีของคุณเองในไดเร็กทอรี Contents / MacOS /


4
คำถามจะถามอย่างชัดเจนว่าจะทำอย่างไรโดยไม่ใช้ xcode ฉันอยู่ในเรือลำเดียวกันและต้องการคำตอบที่แท้จริง
hyperlogic

5
ด้วยสิ่งนี้คุณต้องใช้ XCode เพียงครั้งเดียวในชีวิต :) หรือขอให้ใครสักคนทำเพื่อคุณ
F'x

@ F'x คุณสามารถเผยแพร่. app ที่สร้างขึ้นเป็นตัวอย่างได้หรือไม่?
endolith

3

มีเครื่องมือโอเพนซอร์สบางตัวที่จะช่วยสร้างแอพบันเดิลที่มีไลบรารีที่พึ่งพาสำหรับสภาพแวดล้อมเฉพาะตัวอย่างเช่นpy2appสำหรับแอปพลิเคชันที่ใช้ Python หากคุณไม่พบข้อสรุปทั่วไปคุณอาจปรับให้เข้ากับความต้องการของคุณได้


2

ฉันหวังว่าฉันจะพบโพสต์นี้ก่อนหน้านี้ ....

ต่อไปนี้เป็นวิธีการแก้ปัญหาโดยใช้Run scriptเฟสที่เรียกใช้ทุกครั้งที่สร้างReleaseแอปเวอร์ชัน:

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

หวังว่านี่อาจเป็นประโยชน์กับใครบางคน


0

วิธีแก้ปัญหาเพื่อให้เมนูทำงานบน Mac ด้วยรหัส wxWidget คือ:

  1. เริ่มโปรแกรมในเทอร์มินัลตามปกติ (./appname)
  2. GUI ของโปรแกรมเริ่มทำงานตามปกติและคลิกที่เทอร์มินัลเพื่อให้แอปสูญเสียโฟกัส
  3. คลิก GUI เพื่อฟื้นโฟกัสและรายการเมนูจะทำงาน

ฉันยอมรับว่า App Bundle เป็นวิธีที่ถูกต้องในการสร้างโปรแกรมบน Mac นี่เป็นเพียงวิธีแก้ปัญหาง่ายๆสำหรับความช่วยเหลือในระหว่างการดีบัก

แก้ไข: นี่คือบน Mac Catalina, wxWidgets 3.1.4 พร้อม g ++ 4.2.1 (พ.ย. 2020)

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.