Xây dựng Gói ứng dụng OSX


90

Giả sử tôi đã tạo một ứng dụng osX mà không sử dụng Xcode. Sau khi biên dịch với GCC, tôi nhận được một tệp thực thi được liên kết với một số thư viện khác. Một số thư viện đó có thể lại được liên kết động với các thư viện hệ thống không chuẩn khác

Có công cụ nào tồn tại tạo gói Ứng dụng OSX bằng cách tạo cấu trúc thư mục bắt buộc trước tiên và sau đó sao chép / kiểm tra / sửa lỗi liên kết một cách đệ quy để đảm bảo tất cả các phụ thuộc động cũng nằm trong gói ứng dụng không?

Tôi đoán tôi có thể thử viết một cái gì đó như thế này nhưng tôi đã tự hỏi liệu cái gì đó như thế này đã tồn tại chưa.


4
Tôi không thể sử dụng Xcode vì một số lý do. một trong số đó là do tôi sử dụng gcc tùy chỉnh. Xcode không cho phép tôi chỉ định một gcc khác. Tôi đang sử dụng cmake để tạo trang điểm của mình.
Yogi

>> Một số thư viện trong số đó có thể lại được liên kết động với các thư viện hệ thống không chuẩn khác << Phản hồi đã chọn không hữu ích với trường hợp này. Làm thế nào bạn giải quyết nó?
FlowUI. SimpleUITesting.com

Câu trả lời:


143

Có hai cách để tạo một gói ứng dụng trên MacOSX, Easy và Ugly.

Cách đơn giản là sử dụng XCode. Làm xong.

Vấn đề là đôi khi bạn không thể.

Trong trường hợp của tôi, tôi đang xây dựng một ứng dụng xây dựng các ứng dụng khác. Tôi không thể cho rằng người dùng đã cài đặt XCode. Tôi cũng đang sử dụng MacPorts để xây dựng các thư viện mà ứng dụng của tôi phụ thuộc vào. Tôi cần đảm bảo rằng những dylibs này được đi kèm với ứng dụng trước khi tôi phân phối nó.

Tuyên bố từ chối trách nhiệm: Tôi hoàn toàn không đủ tư cách để viết bài đăng này, mọi thứ trong đó đều được xem xét từ tài liệu của Apple, chọn các ứng dụng hiện có và thử nghiệm và sửa lỗi. Nó phù hợp với tôi, nhưng rất có thể là sai. Vui lòng gửi email cho tôi nếu bạn có bất kỳ chỉnh sửa nào.

Điều đầu tiên bạn nên biết là gói ứng dụng chỉ là một thư mục.
Hãy xem xét cấu trúc của một foo.app giả định.

foo.app/
    Nội dung /
        Info.plist
        Hệ điều hành Mac/
            foo
        Tài nguyên/
            foo.icns

Info.plist là một tệp XML thuần túy. Bạn có thể chỉnh sửa nó bằng trình soạn thảo văn bản hoặc ứng dụng Trình chỉnh sửa danh sách thuộc tính đi kèm với XCode. (Nó nằm trong thư mục / Developer / Applications / Utilities /).

Những điều chính bạn cần bao gồm là:

CFBundleName - Tên của ứng dụng.

CFBundleIcon - Một tệp Biểu tượng giả định nằm trong Dir Nội dung / Tài nguyên. Sử dụng ứng dụng Icon Composer để tạo biểu tượng. (Nó cũng nằm trong thư mục / Developer / Applications / Utilities /) Bạn có thể chỉ cần kéo và thả một png vào cửa sổ của nó và sẽ tự động tạo các mức mip cho bạn.

CFBundleExecutable - Tên của tệp thực thi được giả định nằm trong Contents / MacOS / thư mục con.

Có rất nhiều tùy chọn khác, những tùy chọn được liệt kê ở trên chỉ là mức tối thiểu. Dưới đây là một số tài liệu của Apple về tệp Info.plistcấu trúc gói Ứng dụng .

Ngoài ra, đây là một Info.plist mẫu.

<? xml version = "1.0" encoding = "UTF-8"?>
<! DOCTYPE plist PUBLIC "- // Máy tính Apple // DTD PLIST 1.0 // EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version = "1.0">
<sắc lệnh>
  <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> ÁP DỤNG </string>
  <key> IFMajorVersion </key>
  <integer> 0 </integer>
  <key> IFMinorVersion </key>
  <integer> 1 </integer>
</dict>
</plist>

Trong một thế giới hoàn hảo, bạn chỉ cần thả tệp thực thi của mình vào Contents / MacOS / dir và được thực hiện. Tuy nhiên, nếu ứng dụng của bạn có bất kỳ phụ thuộc dylib không chuẩn nào thì ứng dụng sẽ không hoạt động. Giống như Windows, MacOS đi kèm với loại DLL Hell đặc biệt của riêng nó .

Nếu bạn đang sử dụng MacPorts để xây dựng các thư viện mà bạn liên kết với, vị trí của các dylibs sẽ được mã hóa cứng thành tệp thực thi của bạn. Nếu bạn chạy ứng dụng trên một máy có dylibs ở cùng một vị trí, nó sẽ chạy tốt. Tuy nhiên, hầu hết người dùng sẽ không cài đặt chúng; khi họ nhấp đúp vào ứng dụng của bạn, nó sẽ bị lỗi.

Trước khi phân phối tệp thực thi, bạn sẽ cần thu thập tất cả các dylibs mà nó tải và sao chép chúng vào gói ứng dụng. Bạn cũng sẽ cần chỉnh sửa tệp thực thi để nó tìm kiếm các dylibs ở đúng vị trí. tức là nơi bạn đã sao chép chúng vào.

Chỉnh sửa bằng tay một tệp thực thi nghe có vẻ nguy hiểm phải không? May mắn thay, có các công cụ dòng lệnh để trợ giúp.

otool -L tên_cấu tạo

Lệnh này sẽ liệt kê tất cả các dylibs mà ứng dụng của bạn phụ thuộc vào. Nếu bạn thấy bất kỳ tệp nào KHÔNG nằm trong thư mục Hệ thống / Thư viện hoặc usr / lib, thì đó là những tệp bạn cần sao chép vào gói ứng dụng. Sao chép chúng vào thư mục / Contents / MacOS /. Tiếp theo, bạn sẽ cần chỉnh sửa tệp thực thi để sử dụng dylibs mới.

Trước tiên, bạn cần đảm bảo rằng bạn liên kết bằng cờ -headerpad_max_install_names. Điều này chỉ đảm bảo rằng nếu đường dẫn dylib mới dài hơn đường dẫn trước đó, sẽ có chỗ cho nó.

Thứ hai, sử dụng install_name_tool để thay đổi từng đường dẫn dylib.

install_name_tool -change current_path_to_dylib @ executable_path / blah.dylib Operating_name

Ví dụ thực tế, giả sử ứng dụng của bạn sử dụng libSDL và otool liệt kê vị trí của nó là "/opt/local/lib/libSDL-1.2.0.dylib".

Trước tiên, hãy sao chép nó vào gói ứng dụng.

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

Sau đó, chỉnh sửa tệp thực thi để sử dụng vị trí mới (LƯU Ý: đảm bảo rằng bạn đã tạo nó bằng cờ -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

Chà, chúng ta sắp xong rồi. Bây giờ có một vấn đề nhỏ với thư mục làm việc hiện tại.

Khi bạn khởi động ứng dụng, thư mục hiện tại sẽ là thư mục phía trên nơi chứa ứng dụng. Ví dụ: Nếu bạn đặt foo.app trong thư mục / Applcations thì thư mục hiện tại khi bạn khởi chạy ứng dụng sẽ là thư mục / Applications. Không phải là /Application/foo.app/Contents/MacOS/ như bạn có thể mong đợi.

Bạn có thể thay đổi ứng dụng của mình để giải quyết vấn đề này hoặc bạn có thể sử dụng tập lệnh trình khởi chạy nhỏ kỳ diệu này sẽ thay đổi thư mục hiện tại và khởi chạy ứng dụng của bạn.

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

Đảm bảo bạn điều chỉnh tệp Info.plist để CFBundleExecutable trỏ đến tập lệnh khởi chạy chứ không phải tập lệnh thực thi trước đó.

Ok, tất cả đã xong. May mắn thay, một khi bạn biết tất cả những thứ này, bạn sẽ chôn nó trong một kịch bản xây dựng.


7
+1. Tuy nhiên, con chó thây ma làm tôi hoảng sợ. Bạn có thể sử dụng một bức tranh ít sẹo hơn để minh họa địa ngục DLL không? (Chưa kể rằng thuật ngữ "DLL hell" không được áp dụng. Phương pháp ưu tiên để phân phối các thư viện động là thông qua các khuôn khổ để tránh hầu hết các vấn đề. Tuy nhiên, cách tiếp cận .dylib rất hữu ích cho các thư viện kiểu UNIX.)
Heinrich Apfelmus

1
Làm cách nào để sửa lỗi phụ thuộc kép? Giống như một dylib tham chiếu đến một dylib khác?
FlowUI. SimpleUITesting.com

Không có cách nào để biên dịch bộ điều hành với các vị trí chính xác để bạn không phải chỉnh sửa nó?
đồng bộ

Liệu điều này có còn hoạt động trên Catalina nữa không, với điều kiện an ninh đã trở nên nghiêm ngặt như thế nào? (sửa đổi dylibs, mã nhị phân, v.v.)
đồng bộ hóa

20

Tôi thực sự đã tìm thấy một công cụ rất tiện dụng đáng được ghi nhận ... KHÔNG - Tôi không phát triển công cụ này;)

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

Nó sẽ giải quyết tất cả các phụ thuộc và "sửa chữa" tệp thực thi cũng như các tệp dylib của bạn để hoạt động trơn tru trong gói ứng dụng của bạn.

... nó cũng sẽ kiểm tra các phụ thuộc của các lib động phụ thuộc của bạn: D


đây là một công cụ khá hữu ích! Cảm ơn vì đã chia sẻ và phát triển nó.
xpnimi

Nó thiếu ở đây các phụ thuộc của các phụ thuộc. Sửa chữa?
Peter

9

Tôi sử dụng cái này trong Makefile của mình ... Nó tạo một gói ứng dụng. Hãy đọc và hiểu nó, vì bạn sẽ cần tệp biểu tượng png trong thư mục macosx / cùng với tệp PkgInfo và Info.plist mà tôi đưa vào đây ...

"nó hoạt động trên máy tính của tôi" ... Tôi sử dụng cái này cho nhiều ứng dụng trên 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

Bạn có thể làm cho Info.plist có chỗ dành sẵn văn bản và sau đó sử dụng một cái gì đó như sed hoặc awk để tạo Info.plist cuối cùng với các trường chính xác. Hoặc thậm chí có thể sử dụng plutil để tạo plist.
Mark Heath

8

Giải pháp đơn giản nhất là: tạo một lần một dự án Xcode mà không thay đổi bất kỳ điều gì (tức là giữ ứng dụng một cửa sổ đơn giản mà Xcode tạo cho bạn), xây dựng nó và sao chép gói mà nó đã tạo cho bạn. Sau đó, chỉnh sửa các tệp (đặc biệt là Info.plist) cho phù hợp với nội dung của bạn và đặt tệp nhị phân của riêng bạn vào thư mục Contents / MacOS /.


4
Câu hỏi yêu cầu rõ ràng làm thế nào để làm điều đó mà không có xcode. Tôi đang ở cùng một con thuyền và muốn có một câu trả lời thực sự.
hyperlogic

5
Vâng, với điều này, bạn chỉ phải sử dụng XCode một lần trong đời :) hoặc nhờ ai đó làm điều đó cho bạn.
F'x

@ F'x Bạn có thể xuất bản .app đã tạo đó làm ví dụ ở đâu đó không?
endolith

3

Có một số công cụ mã nguồn mở để giúp xây dựng các gói ứng dụng với các thư viện phụ thuộc cho các môi trường cụ thể, ví dụ: py2app cho các ứng dụng dựa trên Python. Nếu bạn không tìm thấy một cái tổng quát hơn, có lẽ bạn có thể điều chỉnh nó cho phù hợp với nhu cầu của mình.


2

Tôi ước tôi tìm thấy bài đăng này sớm hơn ...

Đây là cách sơ sài của tôi để giải quyết vấn đề này bằng cách sử dụng một Run scriptgiai đoạn được gọi mỗi khi tôi tạo một Releasephiên bản ứng dụng của mình:

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

Hy vọng điều này có thể hữu ích cho ai đó.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.