diff --git a/packaging/macosx/1_install_hb_dependencies.sh b/packaging/macosx/1_install_hb_dependencies.sh new file mode 100755 index 000000000000..6eef43f05917 --- /dev/null +++ b/packaging/macosx/1_install_hb_dependencies.sh @@ -0,0 +1,65 @@ +#!/bin/bash +# +# Script to install required homebrew packages +# + +# Exit in case of error +set -e -o pipefail +trap 'echo "${BASH_SOURCE[0]}{${FUNCNAME[0]}}:${LINENO}: Error: command \`${BASH_COMMAND}\` failed with exit code $?"' ERR + +# Check if brew exists +if ! [ -x "$(command -v brew)" ]; then + echo 'Homebrew not found. Follow instructions as provided by https://brew.sh/ to install it.' >&2 + exit 1 +else + echo "Found homebrew running in $(arch)-based environment." +fi + +# Make sure that homebrew is up-to-date +brew update +brew upgrade + +# Define homebrew dependencies +hbDependencies="adwaita-icon-theme \ + cmake \ + cmocka \ + curl \ + desktop-file-utils \ + exiv2 \ + gettext \ + git \ + glib \ + gmic \ + gphoto2 \ + graphicsmagick \ + gtk-mac-integration \ + gtk+3 \ + icu4c \ + intltool \ + iso-codes \ + jpeg \ + json-glib \ + lensfun \ + libavif \ + libheif \ + libomp \ + librsvg \ + libsecret \ + libsoup@2 \ + little-cms2 \ + llvm \ + lua \ + ninja \ + openexr \ + openjpeg \ + osm-gps-map \ + perl \ + po4a \ + portmidi \ + pugixml \ + sdl2" + +# Install homebrew dependencies +for hbDependency in $hbDependencies; do + brew install "$hbDependency" +done diff --git a/packaging/macosx/2_build_hb_darktable_custom.sh b/packaging/macosx/2_build_hb_darktable_custom.sh new file mode 100755 index 000000000000..67b009b47dbc --- /dev/null +++ b/packaging/macosx/2_build_hb_darktable_custom.sh @@ -0,0 +1,53 @@ +#!/bin/bash +# +# Script to build and install darktable with custom configuration +# + +# Exit in case of error +set -e -o pipefail +trap 'echo "${BASH_SOURCE[0]}{${FUNCNAME[0]}}:${LINENO}: Error: command \`${BASH_COMMAND}\` failed with exit code $?"' ERR + +# Go to directory of script +scriptDir=$(dirname "$0") +cd "$scriptDir"/ +scriptDir=$(pwd) + +# Set variables +buildDir="../../build" +homebrewHome=$(brew --prefix) + +# Build and install darktable here +# ../../build.sh --install --build-type Release --prefix ${PWD} + +# Check for previous attempt and clean +if [[ -d "$buildDir" ]]; then + echo "Deleting directory $buildDir ... " + rm -R "$buildDir" +fi + +# Create directory +mkdir "$buildDir" +cd "$buildDir" + +# Configure build +cmake .. \ + -DCMAKE_OSX_DEPLOYMENT_TARGET=12.0.1 \ + -DCMAKE_CXX_FLAGS=-stdlib=libc++ \ + -DCMAKE_OBJCXX_FLAGS=-stdlib=libc++ \ + -DBINARY_PACKAGE_BUILD=ON \ + -DRAWSPEED_ENABLE_LTO=ON \ + -DBUILD_CURVE_TOOLS=ON \ + -DBUILD_NOISE_TOOLS=ON \ + -DDONT_USE_INTERNAL_LUA=OFF \ + -DBUILD_SSE2_CODEPATHS=OFF \ + -DCMAKE_BUILD_TYPE=RelWithDebInfo \ + -DUSE_COLORD=OFF \ + -DUSE_KWALLET=OFF \ + -DBUILD_CMSTEST=OFF \ + -DCMAKE_INSTALL_PREFIX="$scriptDir" + +# Build using all available cores +make -j"$(sysctl -n hw.ncpu)" + +# Install +make install diff --git a/packaging/macosx/2_build_hb_darktable_default.sh b/packaging/macosx/2_build_hb_darktable_default.sh new file mode 100755 index 000000000000..c9488102294b --- /dev/null +++ b/packaging/macosx/2_build_hb_darktable_default.sh @@ -0,0 +1,25 @@ +#!/bin/bash +# +# Script to build and install darktable with default configuration +# + +# Exit in case of error +set -e -o pipefail +trap 'echo "${BASH_SOURCE[0]}{${FUNCNAME[0]}}:${LINENO}: Error: command \`${BASH_COMMAND}\` failed with exit code $?"' ERR + +# Go to directory of script +scriptDir=$(dirname "$0") +cd "$scriptDir"/ +scriptDir=$(pwd) + +# Set variables +buildDir="../../build" + +# Check for previous attempt and clean +if [[ -d "$buildDir" ]]; then + echo "Deleting directory $buildDir ... " + rm -R "$buildDir" +fi + +# Clean build and install darktable here +../../build.sh --install --build-type Release --prefix "$scriptDir" diff --git a/packaging/macosx/3_make_hb_darktable_package.sh b/packaging/macosx/3_make_hb_darktable_package.sh new file mode 100755 index 000000000000..b34a6f401bc4 --- /dev/null +++ b/packaging/macosx/3_make_hb_darktable_package.sh @@ -0,0 +1,278 @@ +#!/bin/bash +# +# Script to create an application bundle from build files +# +# Usage note: Define CODECERT to properly sign app bundle. As example: +# $ export CODECERT="developer@apple.id" +# The mail address is the email/id of your developer certificate. +# + +# Exit in case of error +set -e -o pipefail +trap 'echo "${BASH_SOURCE[0]}{${FUNCNAME[0]}}:${LINENO}: Error: command \`${BASH_COMMAND}\` failed with exit code $?"' ERR + +# Go to directory of script +scriptDir=$(dirname "$0") +cd "$scriptDir"/ + +# Define base variables +dtPackageDir="package" +dtAppName="darktable" +dtWorkingDir="$dtPackageDir"/"$dtAppName".app +dtResourcesDir="$dtWorkingDir"/Contents/Resources +dtExecDir="$dtWorkingDir"/Contents/MacOS +dtExecutables=$(echo "$dtExecDir"/darktable{,-chart,-cli,-cltest,-generate-cache,-rs-identify,-curve-tool,-noiseprofile}) +homebrewHome=$(brew --prefix) + +# Install direct and transitive dependencies +function install_dependencies { + local hbDependencies + + # Get depedencies of current executable + oToolLDependencies=$(otool -L "$1" 2>/dev/null | grep compatibility | cut -d\( -f1 | sed 's/^[[:blank:]]*//;s/[[:blank:]]*$//' | uniq) + + # Filter for homebrew dependencies + if [[ "$oToolLDependencies" == *"$homebrewHome"* ]]; then + hbDependencies=$(echo "$oToolLDependencies" | grep "$homebrewHome") + fi + + # Check for any homebrew dependencies + if [[ -n "$hbDependencies" && "$hbDependencies" != "" ]]; then + + # Iterate over homebrew dependencies to install them accordingly + for hbDependency in $hbDependencies; do + # Skip dependency if it is a dependency of itself + if [[ "$hbDependency" != "$1" ]]; then + + # Store file name + dynDepOrigFile=$(basename "$hbDependency") + dynDepTargetFile="$dtResourcesDir/lib/$dynDepOrigFile" + + # Install dependency if not yet existant + if [[ ! -f "$dynDepTargetFile" ]]; then + echo "Installing dependency <$hbDependency> of <$1>." + + # Copy dependency as not yet existant + cp -L "$hbDependency" "$dynDepTargetFile" + + # Handle transitive dependencies + install_dependencies "$dynDepTargetFile" + fi + fi + done + + fi +} + +# Reset executable path to relative path +# Background: see e.g. http://clarkkromenaker.com/post/library-dynamic-loading-mac/ +function reset_exec_path { + local hbDependencies + + # Get shared libraries used of current executable + oToolLDependencies=$(otool -L "$1" 2>/dev/null | grep compatibility | cut -d\( -f1 | sed 's/^[[:blank:]]*//;s/[[:blank:]]*$//' | uniq) + + # Handle libdarktable.dylib + if [[ "$oToolLDependencies" == *"@rpath/libdarktable.dylib"* && "$1" != *"libdarktable.dylib"* ]]; then + echo "Resetting loader path for libdarktable.dylib of <$1>" + install_name_tool -rpath @loader_path/../lib/darktable @loader_path/../Resources/lib/darktable "$1" + fi + + # Filter for any homebrew specific paths + if [[ "$oToolLDependencies" == *"$homebrewHome"* ]]; then + hbDependencies=$(echo "$oToolLDependencies" | grep "$homebrewHome") + fi + + # Check for any homebrew dependencies + if [[ -n "$hbDependencies" && "$hbDependencies" != "" ]]; then + + # Iterate over homebrew dependencies to reset path accordingly + for hbDependency in $hbDependencies; do + + # Store file name + dynDepOrigFile=$(basename "$hbDependency") + dynDepTargetFile="$dtResourcesDir/lib/$dynDepOrigFile" + + echo "Resetting executable path for dependency <$hbDependency> of <$1>" + + # Set correct executable path + install_name_tool -change "$hbDependency" "@executable_path/../Resources/lib/$dynDepOrigFile" "$1" + done + + fi + + # Get shared library id name of current executable + oToolDDependencies=$(otool -D "$1" 2>/dev/null | sort | uniq) + + # Set correct ID to new destination if required + if [[ "$oToolDDependencies" == *"$homebrewHome"* ]]; then + + # Store file name + libraryOrigFile=$(basename "$1") + + echo "Resetting library ID of <$1>" + + # Set correct library id + install_name_tool -id "@executable_path/../Resources/lib/$libraryOrigFile" "$1" + fi +} + +# Search and install any translation files +function install_translations { + + # Find relevant translation files + translationFiles=$(find "$homebrewHome"/share/locale -name "$1".mo) + + for srcTranslFile in $translationFiles; do + + # Define target filename + targetTranslFile=${srcTranslFile//"$homebrewHome"/"$dtResourcesDir"} + + # Create directory if not yet existing + targetTranslDir=$(dirname "$targetTranslFile") + if [[ ! -d "$targetTranslDir" ]]; then + mkdir -p "$targetTranslDir" + fi + # Copy translation file + cp -L "$srcTranslFile" "$targetTranslFile" + done +} + +# Install share directory +function install_share { + + # Define source and target directory + srcShareDir="$homebrewHome/share/$1" + targetShareDir="$dtResourcesDir/share/" + + # Copy share directory + cp -RL "$srcShareDir" "$targetShareDir" +} + +# Check for previous attempt and clean +if [[ -d "$dtPackageDir" ]]; then + echo "Deleting directory $dtPackageDir ... " + chown -R "$USER" "$dtPackageDir" + rm -Rf "$dtPackageDir" +fi + +# Create basic structure +mkdir -p "$dtExecDir" +mkdir -p "$dtResourcesDir"/share/applications +mkdir -p "$dtResourcesDir"/etc/gtk-3.0 + +# Add basic elements +cp Info.plist "$dtWorkingDir"/Contents/ +echo "APPL$dtAppName" >>"$dtWorkingDir"/Contents/PkgInfo +cp Icons.icns "$dtResourcesDir"/ + +# Set version information +sed -i '' 's|{VERSION}|'$(git describe --tags --long --match '*[0-9.][0-9.][0-9]' | cut -d- -f2 | sed 's/^\([0-9]*\.[0-9]*\)$/\1.0/')'|' "$dtWorkingDir"/Contents/Info.plist +sed -i '' 's|{COMMITS}|'$(git describe --tags --long --match '*[0-9.][0-9.][0-9]' | cut -d- -f3)'|' "$dtWorkingDir"/Contents/Info.plist + +# Generate settings.ini +echo "[Settings] +gtk-icon-theme-name = Adwaita +" >"$dtResourcesDir"/etc/gtk-3.0/settings.ini + +# Add darktable executables +cp bin/darktable{,-chart,-cli,-cltest,-generate-cache,-rs-identify} "$dtExecDir"/ + +# Add darktable tools if existent +if [[ -d libexec/darktable/tools ]]; then + cp libexec/darktable/tools/* "$dtExecDir"/ +fi + +# Add darktable directories +cp -R {lib,share} "$dtResourcesDir"/ + +# Install homebrew dependencies of darktable executables +for dtExecutable in $dtExecutables; do + if [[ -f "$dtExecutable" ]]; then + install_dependencies "$dtExecutable" + fi +done + +# Add homebrew shared objects +dtSharedObjDirs="gtk-3.0 libgphoto2 libgphoto2_port gdk-pixbuf-2.0 gio" +for dtSharedObj in $dtSharedObjDirs; do + cp -LR "$homebrewHome"/lib/"$dtSharedObj" "$dtResourcesDir"/lib/ +done + +# Add homebrew translations +dtTranslations="gtk30 gtk30-properties gtk-mac-integration iso_639-2 gphoto2" +for dtTranslation in $dtTranslations; do + install_translations "$dtTranslation" +done + +# Add homebrew share directories +dtShares="lensfun icons iso-codes mime" +for dtShare in $dtShares; do + install_share "$dtShare" +done + +# Update icon caches +gtk3-update-icon-cache -f "$dtResourcesDir"/share/icons/Adwaita +gtk3-update-icon-cache -f "$dtResourcesDir"/share/icons/hicolor + +# Try updating lensfun +lensfun-update-data || true +lfLatestData="$HOME"/.local/share/lensfun/updates/version_1 +if [[ -d "$lfLatestData" ]]; then + echo "Adding latest lensfun data from $lfLatestData." + cp -R "$lfLatestData" "$dtResourcesDir"/share/lensfun/ +fi + +# Add glib gtk settings schemas +glibSchemasDir="$dtResourcesDir"/share/glib-2.0/schemas +if [[ ! -d "$glibSchemasDir" ]]; then + mkdir -p "$glibSchemasDir" +fi +cp -L "$homebrewHome"/share/glib-2.0/schemas/org.gtk.Settings.*.gschema.xml "$glibSchemasDir"/ +# Compile glib schemas +glib-compile-schemas "$dtResourcesDir"/share/glib-2.0/schemas/ + +# Define gtk-query-immodules-3.0 +immodulesCacheFile="$dtResourcesDir"/lib/gtk-3.0/3.0.0/immodules.cache +gtkVersion=$(pkg-config --modversion gtk+-3.0) +sed -i '' "s#$homebrewHome/Cellar/gtk+3/$gtkVersion/lib/gtk-3.0/3.0.0/immodules#@executable_path/../Resources/lib/gtk-3.0/3.0.0/immodules#g" "$immodulesCacheFile" +sed -i '' "s#$homebrewHome/Cellar/gtk+3/$gtkVersion/share/locale#@executable_path/../Resources/share/locale#g" "$immodulesCacheFile" +# Rename and move it to the right place +mv "$immodulesCacheFile" "$dtResourcesDir"/etc/gtk-3.0/gtk.immodules + +# Define gdk-pixbuf-query-loaders +loadersCacheFile="$dtResourcesDir"/lib/gdk-pixbuf-2.0/2.10.0/loaders.cache +sed -i '' "s#$homebrewHome/lib/gdk-pixbuf-2.0/2.10.0/loaders#@executable_path/../Resources/lib/gdk-pixbuf-2.0/2.10.0/loaders#g" "$loadersCacheFile" +# Move it to the right place +mv "$loadersCacheFile" "$dtResourcesDir"/etc/gtk-3.0/ + +# Install homebrew dependencies of lib subdirectories +dtLibFiles=$(find -E "$dtResourcesDir"/lib/*/* -regex '.*\.(so|dylib)') +for dtLibFile in $dtLibFiles; do + install_dependencies "$dtLibFile" +done + +# Reset executable paths to relative path +dtExecFiles="$dtExecutables" +dtExecFiles+=" " +dtExecFiles+=$(find -E "$dtResourcesDir"/lib -regex '.*\.(so|dylib)') +for dtExecFile in $dtExecFiles; do + if [[ -f "$dtExecFile" ]]; then + reset_exec_path "$dtExecFile" + fi +done + +# Add gtk files +cp defaults.list "$dtResourcesDir"/share/applications/ +cp open.desktop "$dtResourcesDir"/share/applications/ + +# Sign app bundle +if [ -n "$CODECERT" ]; then + # Use certificate if one has been provided + find package/darktable.app/Contents/Resources/lib -type f -exec codesign --verbose --force --options runtime -i "org.darktable" -s "${CODECERT}" \{} \; + codesign --deep --verbose --force --options runtime -i "org.darktable" -s "${CODECERT}" package/darktable.app +else + # Use ad-hoc signing and preserve metadata + find package/darktable.app/Contents/Resources/lib -type f -exec codesign --verbose --force --preserve-metadata=entitlements,requirements,flags,runtime -i "org.darktable" -s - \{} \; + codesign --deep --verbose --force --preserve-metadata=entitlements,requirements,flags,runtime -i "org.darktable" -s - package/darktable.app +fi diff --git a/packaging/macosx/4_make_hb_darktable_dmg.sh b/packaging/macosx/4_make_hb_darktable_dmg.sh new file mode 100755 index 000000000000..6f763b600984 --- /dev/null +++ b/packaging/macosx/4_make_hb_darktable_dmg.sh @@ -0,0 +1,56 @@ +#!/bin/bash +# +# Script to generate DMG image from application bundle +# + +# Exit in case of error +set -e -o pipefail +trap 'echo "${BASH_SOURCE[0]}{${FUNCNAME[0]}}:${LINENO}: Error: command \`${BASH_COMMAND}\` failed with exit code $?"' ERR + +# Define application name +PROGN=darktable + +# Go to directory of script +scriptDir=$(dirname "$0") +cd "$scriptDir"/ + +# Generate symlink to applications folder for easier drag & drop within dmg +ln -s /Applications package/ || true + +# Create temporary rw image +hdiutil create -srcfolder package -volname "${PROGN}" -fs HFS+ \ + -fsargs "-c c=64,a=16,e=16" -format UDRW pack.temp.dmg + +# Mount image without autoopen to create window style params +device=$(hdiutil attach -readwrite -noverify -autoopen "pack.temp.dmg" | + egrep '^/dev/' | sed 1q | awk '{print $1}') + +echo ' + tell application "Finder" + tell disk "'${PROGN}'" + set current view of container window to icon view + set toolbar visible of container window to false + set statusbar visible of container window to false + set the bounds of container window to {400, 100, 885, 330} + set theViewOptions to the icon view options of container window + set arrangement of theViewOptions to not arranged + set icon size of theViewOptions to 72 + set position of item "'${PROGN}'" of container window to {100, 100} + set position of item "Applications" of container window to {375, 100} + update without registering applications + end tell + end tell +' | osascript + +# Finalizing creation +chmod -Rf go-w /Volumes/"${PROGN}" +sync +hdiutil detach ${device} +DMG="${PROGN}-$(git describe --tags | sed 's/^release-//;s/-/+/;s/-/~/;s/rc/~rc/')-$(arch)" +hdiutil convert "pack.temp.dmg" -format UDZO -imagekey zlib-level=9 -o "${DMG}" +rm -f pack.temp.dmg + +# Sign dmg image when a certificate has been provided +if [ -n "$CODECERT" ]; then + codesign --deep --verbose --force --options runtime -i "org.darktable" -s "${CODECERT}" "${DMG}".dmg +fi diff --git a/packaging/macosx/BUILD_hb.txt b/packaging/macosx/BUILD_hb.txt new file mode 100644 index 000000000000..b5396a72e36a --- /dev/null +++ b/packaging/macosx/BUILD_hb.txt @@ -0,0 +1,47 @@ +How to make disk image with darktable application bundle from source (using Homebrew): + +0). Install Homebrew (instructions and prerequisites can be found on official website https://brew.sh/), ideally use default installation path (/opt/homebrew for arm64, /usr/local for i386). + +1). Install required homebrew packages: + $ 1_install_hb_dependencies.sh + +2). Build and install darktable using either option A or B: + - Option A: Build using the default build.sh, which should work for most use cases + $ 2_build_hb_darktable_default.sh + - Option B: Build using custom cmake options, edit according to your specific needs/enviroment + $ 2_build_hb_darktable_custom.sh + +3). Create application bundle from build files. To properly sign the app bundle you can optionally provide your developer certificate email/id by defining CODECERT: + $ export CODECERT="your.developer@apple.id" # optional, not required + $ 3_make_hb_darktable_package.sh + +4). Generate DMG image from application bundle: + $ 4_make_hb_darktable_dmg.sh + +The final result is a DMG file: darktable-+-{arm64|i386}.dmg + +Y). Forcefully remove any remainder of previous attemps + $ Y_cleanup_hb_darktable.sh + +LIMITATIONS: +- Created DMG will only be compatible to the macOS version it was created upon. +- Naturally the libraries that darktable is built upon will be as good as its currently provided homebrew packages. You might want to use "$ brew pin " to lock your working/verified setup. +- As of today homebrew ships lensfun 0.3.3 that is the successor of the last stable release 0.3.2. It is expected to be compatible and should not break existing edits based on 0.3.2 or before. +- For now additional darktable tools like darktable-curve-tool or darktable-noiseprofile are not part of the default application bundle. + +MACOS SECURITY: +- The DMG is not notarized with/at Apple by using this approach. If it is still required see the official BUILD.txt for further instructions. +- As the DMG is not notarized and the app bundle may not even be properly signed, it is still possible to install/run darktable at your own risk. To do so make sure to run "$ xattr -d com.apple.quarantine .dmg" on the DMG before installing. + +NOTES: +- It will be automatically build for the architecture you are currently on, either Apple Silicon (arm64) or Intel (i386). +- If you want to build for i386 on arm64 see https://stackoverflow.com/questions/64951024/how-can-i-run-two-isolated-installations-of-homebrew/68443301#68443301 about how to handle both enviroments in parallel. +- After creating the darktable application bundle (step 3) you can directly run the result by executing: + $ package/darktable.app/Contents/MacOS/darktable --configdir .config/darktable/ --cachedir .cache/darktable/ + +REFERENCES: +This approach is heavily based on and inspired by: +- The official BUILD.txt instructions (MacPorts-based) by the darktable community +- http://clarkkromenaker.com/post/library-dynamic-loading-mac/ +- https://gitlab.gnome.org/GNOME/gtk-mac-bundler +- https://github.com/auriamg/macdylibbundler/ diff --git a/packaging/macosx/Y_cleanup_hb_darktable.sh b/packaging/macosx/Y_cleanup_hb_darktable.sh new file mode 100755 index 000000000000..31c9cfc60206 --- /dev/null +++ b/packaging/macosx/Y_cleanup_hb_darktable.sh @@ -0,0 +1,17 @@ +#!/bin/bash +# +# Script to forcefully remove any remainder of previous attemps +# + +removableDirs="../../build bin lib libexec share package .config .cache" + +for i in $removableDirs; do + + # Delete build directory + if [[ -d "$i" ]]; then + echo "Deleting directory $i ... " + chown -R "$USER" "$i" + rm -Rf "$i" + fi + +done