[macos] OSX App Bundle 빌드

Xcode를 사용하지 않고 osX 앱을 만들었다 고 가정 해 보겠습니다. GCC로 컴파일 한 후 여러 다른 라이브러리에 연결된 실행 파일을 얻습니다. 이러한 라이브러리 중 일부는 다른 비표준 시스템 라이브러리에 다시 동적으로 링크 될 수 있습니다.

먼저 필요한 디렉터리 구조를 만든 다음 모든 동적 종속성이 App Bundle에 있는지 확인하기 위해 링크를 반복적으로 복사 / 확인 / 수정하여 OSX App Bundle을 만드는 도구가 있습니까?

이런 식으로 써볼 수 있겠지만 이런 게 이미 존재하는지 궁금합니다.



답변

MacOSX에서 앱 번들을 생성하는 방법에는 Easy와 Ugly의 두 가지가 있습니다.

쉬운 방법은 XCode를 사용하는 것입니다. 끝난.

문제는 때때로 당신이 할 수 없다는 것입니다.

제 경우에는 다른 앱을 빌드하는 앱을 만들고 있습니다. 사용자가 XCode를 설치했다고 가정 할 수 없습니다. 또한 MacPorts 를 사용하여 내 앱이 의존하는 라이브러리를 구축하고 있습니다. 배포하기 전에 이러한 dylib가 앱과 함께 번들로 제공되는지 확인해야합니다.

면책 조항 : 나는이 게시물을 작성할 자격이 없습니다. Apple 문서에서 모든 것이 빛나고 기존 앱과 시행 착오를 골라냅니다. 그것은 나를 위해 작동하지만 대부분 잘못되었습니다. 수정 사항이 있으면 이메일을 보내주십시오.

가장 먼저 알아야 할 것은 앱 번들은 단지 디렉토리라는 것입니다.
가상 foo.app의 구조를 살펴 보겠습니다.

foo.app/
    내용/
        Info.plist
        맥 OS/
            foo
        자원/
            foo.icns

Info.plist는 일반 XML 파일입니다. 텍스트 편집기 또는 XCode와 함께 제공되는 Property List Editor 앱으로 편집 할 수 있습니다. (/ Developer / Applications / Utilities / 디렉토리에 있습니다).

포함해야하는 주요 사항은 다음과 같습니다.

CFBundleName- 앱의 이름입니다.

CFBundleIcon -Contents / Resources 디렉토리에있는 것으로 간주되는 아이콘 파일입니다. Icon Composer 앱을 사용하여 아이콘을 만듭니다. (또한 / Developer / Applications / Utilities / 디렉토리에 있습니다.) png를 창에 끌어다 놓기 만하면 자동으로 밉 레벨이 생성됩니다.

CFBundleExecutable -Contents / MacOS / 하위 폴더에있는 것으로 간주되는 실행 파일의 이름입니다.

더 많은 옵션이 있으며 위에 나열된 옵션은 최소한의 것입니다. 다음은 Info.plist
파일 및
App Bundle 구조 에 대한 Apple 문서입니다
.

또한 다음은 샘플 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> 발 </ 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>
  <문자열> 0.01 </ 문자열>
  <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 Hell 과 함께 제공됩니다 .

당신이 사용하는 경우 MacPorts를 당신이에 연결하는 것이 빌드 라이브러리를하여 dylibs의 위치는 하드 코드 실행 파일에있을 것입니다. 정확히 동일한 위치에 dylib가있는 컴퓨터에서 앱을 실행하면 제대로 실행됩니다. 그러나 대부분의 사용자는이를 설치하지 않습니다. 앱을 두 번 클릭하면 충돌이 발생합니다.

실행 파일을 배포하기 전에로드하는 모든 dylib를 수집하고 앱 번들에 복사해야합니다. 또한 올바른 위치에서 dylib를 찾을 수 있도록 실행 파일을 편집해야합니다. 즉, 복사 한 위치.

실행 파일을 손으로 편집하는 것이 위험한 것 같습니까? 다행히도 도움이되는 명령 줄 도구가 있습니다.

otool -L 실행 파일 이름

이 명령은 앱이 의존하는 모든 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”로 나열 한다고 가정 해 보겠습니다 .

먼저 앱 번들에 복사합니다.

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

휴, 거의 끝났습니다. 이제 현재 작업 디렉토리에 작은 문제가 있습니다.

앱을 시작할 때 현재 디렉토리는 애플리케이션이있는 위의 디렉토리가됩니다. 예 : / Applcations 폴더에 foo.app을 배치하면 앱을 시작할 때 현재 디렉토리가 / Applications 폴더가됩니다. 예상대로 /Applications/foo.app/Contents/MacOS/가 아닙니다.

이를 고려하여 앱을 변경하거나 현재 디렉토리를 변경하고 앱을 실행하는이 마법의 작은 실행기 스크립트를 사용할 수 있습니다.

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

CFBundleExecutable 이 이전 실행 파일이 아닌 실행 스크립트를 가리 키
도록 Info.plist 파일을 조정해야합니다 .

이제 모두 완료되었습니다. 운 좋게도이 모든 것을 알고 나면 빌드 스크립트에 묻습니다.


답변

나는 실제로 약간의 신용을받을 가치가있는 매우 편리한 도구를 발견했다. … 아니오-나는 이것을 개발하지 않았다;)

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

모든 종속성을 해결하고 실행 파일과 dylib 파일을 “수정”하여 앱 번들에서 원활하게 작동합니다.

… 또한 종속 동적 라이브러리의 종속성을 확인합니다. : D


답변

내 Makefile에서 사용합니다. 앱 번들을 생성합니다. 여기에 포함 된 PkgInfo 및 Info.plist 파일과 함께 macosx / 폴더에 png 아이콘 파일이 필요하기 때문에 읽고 이해하십시오.

“내 컴퓨터에서 작동합니다”… 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


답변

가장 간단한 해결책은 아무것도 변경하지 않고 Xcode 프로젝트를 한 번 생성하고 (즉, Xcode가 생성하는 간단한 단일 창 앱 유지) 빌드 한 다음 생성 한 번들을 복사하는 것입니다. 그런 다음 콘텐츠에 맞게 파일 (특히 Info.plist)을 편집하고 자체 바이너리를 Contents / MacOS / 디렉토리에 넣습니다.


답변

특정 환경 (예 : Python 기반 애플리케이션 용 py2app) 을 위한 종속 라이브러리로 App Bundle을 빌드하는 데 도움이되는 몇 가지 오픈 소스 도구가 있습니다. 더 일반적인 것을 찾지 못한 경우 필요에 맞게 조정할 수 있습니다.


답변

이 게시물을 더 일찍 찾았 으면 좋겠습니다 ….

다음 Run scriptRelease앱 버전을 빌드 할 때마다 호출 되는 단계를 사용하여이 문제를 해결하는 스케치 방법입니다 .

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

이것이 누군가에게 유용 할 수 있기를 바랍니다.


답변