OSXアプリバンドルの構築


90

Xcodeを使用せずにosXアプリを作成したとします。GCCでコンパイルした後、他のいくつかのライブラリにリンクされている実行可能ファイルを取得します。これらのライブラリの一部は、他の非標準システムライブラリに動的にリンクされている可能性があります

最初に必要なディレクトリ構造を作成し、次にリンクを再帰的にコピー/チェック/修正してすべての動的依存関係もアプリバンドルに含まれていることを確認することでOSXアプリバンドルを作成するツールはありますか?

このようなものを書いてみることができると思いますが、このようなものはすでに存在するのだろうかと思っていました。


4
いくつかの理由でXcodeを使用できません。その1つは、カスタムgccを使用しているためです。Xcodeでは、別のgccを指定することはできません。私はcmakeを使用してメイクファイルを作成しています。
ヨギ

>>これらのライブラリの一部は、他の非標準システムライブラリに動的にリンクされている可能性があります<<選択した応答はこの場合には役立ちません。どのように解決しましたか?
FlowUI。SimpleUITesting.com 2018年

回答:


143

MacOSXでアプリバンドルを作成するには、EasyとUglyの2つの方法があります。

簡単な方法は、XCodeを使用することです。完了。

問題は時々あなたができないことです。

私の場合、他のアプリを構築するアプリを構築しています。ユーザーがXCodeをインストールしているとは思いません。また、MacPortsを使用して、アプリが依存するライブラリを構築しています。配布する前に、これらのdylibがアプリにバンドルされていることを確認する必要があります。

免責事項:私はこの投稿を書く資格がまったくありません。すべてがAppleのドキュメントから集められており、既存のアプリと試行錯誤を分解しています。それは私にとってはうまくいきますが、おそらく間違っています。訂正があれば私にメールしてください。

最初に知っておくべきことは、アプリバンドルは単なるディレクトリであるということです。
架空のfoo.appの構造を調べてみましょう。

foo.app/
    内容/
        Info.plist
        マックOS/
            foo
        リソース/
            foo.icns

Info.plistはプレーンなXMLファイルです。テキストエディタまたはXCodeにバンドルされているプロパティリストエディタアプリを使用して編集できます。(/ Developer / Applications / Utilities /ディレクトリにあります)。

含める必要がある重要なものは次のとおりです。

CFBundleName-アプリの名前。

CFBundleIcon -Contents / Resourcesディレクトリにあると想定されるアイコンファイル。IconComposerアプリを使用してアイコンを作成します。(これは/ Developer / Applications / Utilities /ディレクトリにもあります)pngをウィンドウにドラッグアンドドロップするだけで、ミップレベルが自動的に生成されます。

CFBundleExecutable -Contents / MacOS /サブフォルダーにあると想定される実行可能ファイルの名前。

他にもたくさんのオプションがありますが、上記のオプションは最低限のものにすぎません。Info.plist ファイルと AppBundleの構造に関する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">
<辞書>
  <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には独自の特別な種類のDLLHellが付属しています

MacPortsを 使用してリンク先のライブラリを構築している場合、dylibの場所は実行可能ファイルにハードコードされます。dylibがまったく同じ場所にあるマシンでアプリを実行すると、正常に実行されます。ただし、ほとんどのユーザーはそれらをインストールしません。彼らがあなたのアプリをダブルクリックすると、それはただクラッシュするでしょう。

実行可能ファイルを配布する前に、実行可能ファイルがロードするすべてのdylibを収集し、アプリバンドルにコピーする必要があります。また、実行可能ファイルを編集して、正しい場所でdylibを検索するようにする必要があります。つまり、それらをコピーした場所です。

実行可能ファイルを手作業で編集するのは危険ですね。幸いなことに、役立つコマンドラインツールがあります。

otool-L実行可能ファイル名

このコマンドは、アプリが依存するすべてのdylibを一覧表示します。System / Libraryフォルダーまたはusr / libフォルダーにないものがある場合は、それらをアプリバンドルにコピーする必要があります。それらを/ Contents / MacOS /フォルダーにコピーします。次に、新しいdylibを使用するように実行可能ファイルを編集する必要があります。

まず、-headerpad_max_install_namesフラグを使用してリンクしていることを確認する必要があります。これにより、新しいdylibパスが前のパスよりも長い場合、そのための余地があることが保証されます。

次に、install_name_toolを使用して各dylibパスを変更します。

install_name_tool-既存の_path_to_dylibを変更@executable_path /blah.dylib実行可能ファイル名

実際の例として、アプリが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 @ executeable_path / libSDL-1.2.0.dylib foo.app/Contents/MacOS/foo

ふぅ、ほぼ完了です。現在の作業ディレクトリに小さな問題があります。

アプリを起動すると、現在のディレクトリは、アプリケーションが配置されている場所の上のディレクトリになります。次に例を示します。foo.appを/ Applcationsフォルダーに配置すると、アプリを起動したときの現在のディレクトリは/ Applicationsフォルダーになります。ご想像のとおり、/ Applications / foo.app / Contents / MacOS /ではありません。

これを考慮してアプリを変更することも、現在のディレクトリを変更してアプリを起動するこの魔法の小さなランチャースクリプトを使用することもできます。

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

CFBundleExecutableが前の実行可能ファイルではなく起動スクリプトを指す ように、Info.plistファイルを調整してください。

はい、すべて完了しました。幸いなことに、これらすべてを理解したら、ビルドスクリプトに埋め込みます。


7
+1。しかし、ゾンビ犬は私をびっくりさせます。DLL地獄を説明するために、傷の少ない画像を使用できますか?(「DLL地獄」という用語が適用されないことは言うまでもありません。ダイナミックライブラリを配布するための推奨される方法は、ほとんどの問題を回避するフレームワークを使用することです。それでも、.dylibアプローチはUNIXスタイルのライブラリに役立ちます。)
Heinrich Apfelmus

1
二重依存関係を修正するにはどうすればよいですか?dylibが別のdylibを参照するように?
FlowUI。SimpleUITesting.com 2018年

executibleを正しい場所でコンパイルする方法がないので、編集する必要はありませんか?
シンクロナイザー

セキュリティがどれほど厳しくなったかを考えると、これはカタリナでも機能するでしょうか?(dylib、バイナリなどの変更)
シンクロナイザー

20

私は実際にいくつかの信用に値する非常に便利なツールを見つけました...いいえ-私はこれを開発しませんでした;)

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

すべての依存関係を解決し、実行可能ファイルとdylibファイルを「修正」して、アプリバンドルでスムーズに機能するようにします。

...依存する動的ライブラリの依存関係もチェックします:D


これはかなり便利なツールです!共有して開発してくれてありがとう。
xpnimi

ここでは、依存関係の依存関係が欠落しています。修正しますか?
ピーター

9

これをMakefileで使用しています...アプリバンドルを作成します。ここに含めるPkgInfoファイルとInfo.plistファイルと一緒にmacosx /フォルダーにpngアイコンファイルが必要になるため、読んで理解してください...

「それは私のコンピューターで動作します」...私はこれをマーベリックスの複数のアプリに使用しています...

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

Info.plistにテキストプレースホルダーを設定し、sedやawkなどを使用して、正しいフィールドを持つ最終的なInfo.plistを作成できます。または、plutilを使用してplistを作成することもできます。
マークヒース

8

最も簡単な解決策は、何も変更せずにXcodeプロジェクトを1回作成し(つまり、Xcodeが作成する単純な1ウィンドウアプリを保持し)、ビルドして、作成したバンドルをコピーすることです。次に、コンテンツに合わせてファイル(特にInfo.plist)を編集し、独自のバイナリをContents / MacOS /ディレクトリに配置します。


4
質問は、xcodeなしでそれを行う方法を明示的に尋ねます。私は同じ船に乗っているので、本当の答えが欲しいです。
ハイパーロジック2010

5
さて、これで、あなたはあなたの人生で一度だけXCodeを使う必要があります:)または誰かにあなたのためにそれをするように頼む必要があります。
f'x 2010

@ F'x生成された.appを例としてどこかに公開できますか?
endolith

3

Pythonベースのアプリケーション用のpy2appなど、特定の環境用の依存ライブラリを使用してアプリバンドルを構築するのに役立つオープンソースツールがいくつかあります。より一般的なものが見つからない場合は、おそらくニーズに合わせて調整できます。


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)

これが誰かに役立つことを願っています。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.