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 를 사용하여 링크하는 라이브러리를 빌드하는 경우 dylib의 위치가 실행 파일에 하드 코딩됩니다. 정확히 동일한 위치에 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-기존 _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
가장 간단한 해결책은 아무것도 변경하지 않고 Xcode 프로젝트를 한 번 생성하고 (즉, Xcode가 생성하는 간단한 단일 창 앱 유지) 빌드 한 다음 생성 한 번들을 복사하는 것입니다. 그런 다음 콘텐츠에 맞게 파일 (특히 Info.plist)을 편집하고 자체 바이너리를 Contents / MacOS / 디렉토리에 넣습니다.
내 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
특정 환경 (예 : Python 기반 애플리케이션 용 py2app) 을 위한 종속 라이브러리로 앱 번들을 빌드하는 데 도움이되는 몇 가지 오픈 소스 도구가 있습니다. 더 일반적인 것을 찾지 못한 경우 필요에 맞게 조정할 수 있습니다.
이 게시물을 더 일찍 찾았 으면 좋겠습니다 ....
내 앱 버전을 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)
이것이 누군가에게 유용 할 수 있기를 바랍니다.
참고 URL : https://stackoverflow.com/questions/1596945/building-osx-app-bundle
'Programing' 카테고리의 다른 글
| MySQL / 쓰기 파일 오류 (Errcode 28) (0) | 2020.10.12 |
|---|---|
| ActionSheet가 작동하지 않는 iPad (0) | 2020.10.12 |
| F # : mutable 대 ref (0) | 2020.10.12 |
| Haskell : 왜 도우미 함수의 이름을“go”로 명명하는 규칙이 있습니까? (0) | 2020.10.12 |
| C #에 대한 더 나은 대기 패턴이 있습니까? (0) | 2020.10.12 |