summaryrefslogtreecommitdiffstats
path: root/packaging/macos
diff options
context:
space:
mode:
Diffstat (limited to 'packaging/macos')
-rw-r--r--packaging/macos/020-vars.sh207
-rw-r--r--packaging/macos/030-funcs.sh406
-rwxr-xr-xpackaging/macos/110-sysprep.sh40
-rwxr-xr-xpackaging/macos/130-jhbuild-bootstrap.sh69
-rwxr-xr-xpackaging/macos/140-jhbuild-gtk3.sh19
-rwxr-xr-xpackaging/macos/150-jhbuild-inkdeps.sh37
-rwxr-xr-xpackaging/macos/160-jhbuild-other.sh61
-rwxr-xr-xpackaging/macos/210-inkscape-build.sh59
-rwxr-xr-xpackaging/macos/220-inkscape-package.sh175
-rwxr-xr-xpackaging/macos/230-inkscape-distrib.sh51
-rw-r--r--packaging/macos/README.md106
-rwxr-xr-xpackaging/macos/build_inkscape.sh21
-rwxr-xr-xpackaging/macos/build_toolset.sh44
-rw-r--r--packaging/macos/fonts.conf77
-rw-r--r--packaging/macos/inkscape.bundle165
-rw-r--r--packaging/macos/inkscape.plist565
-rw-r--r--packaging/macos/inkscape_dmg.py246
-rwxr-xr-xpackaging/macos/install_toolset.sh66
-rwxr-xr-xpackaging/macos/uninstall_toolset.sh36
19 files changed, 2450 insertions, 0 deletions
diff --git a/packaging/macos/020-vars.sh b/packaging/macos/020-vars.sh
new file mode 100644
index 0000000..98f7a1b
--- /dev/null
+++ b/packaging/macos/020-vars.sh
@@ -0,0 +1,207 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# This file is part of the build pipeline for Inkscape on macOS.
+#
+# ### 020-vars.sh ###
+# This file contains all the global variables (as in: configuration for the
+# build pipeline) and gets sourced by all the other scripts.
+# If you want to override settings, the suggested way is that you create a
+# `0nn-custom.sh` file and put them there. All files named '0nn-*.sh' get
+# sourced in numerical order.
+
+[ -z $VARS_INCLUDED ] && VARS_INCLUDED=true || return # include guard
+
+### name and directory #########################################################
+
+SELF_NAME=$(basename $0)
+
+### nesting level when trapping errors #########################################
+
+ERRTRACE_COUNT=0
+
+### multithreading #############################################################
+
+CORES=$(sysctl -n hw.ncpu) # use all available cores
+export MAKEFLAGS="-j $CORES"
+
+### target OS version ##########################################################
+
+# The current build setup is
+# - Xcode 12
+# - OS X El Capitan 10.11 SDK (part of Xcode 7.3.1)
+# - macOS Catalina 10.15.6
+
+SDK_VERSION=10.11
+[ -z $SDKROOT_DIR ] && SDKROOT_DIR=/opt/sdks
+export SDKROOT=$SDKROOT_DIR/MacOSX${SDK_VERSION}.sdk
+
+### build system/toolset version ###############################################
+
+TOOLSET_VERSION=0.37.6
+
+### ramdisk ####################################################################
+
+# Using the toolset dmg, a small writable overlay is required.
+OVERLAY_RAMDISK_SIZE=3 # unit is GiB
+
+### toolset root directory #####################################################
+
+# This is the main directory where all the action takes place below. It is
+# one level above WRK_DIR (which in previous releases has been called the
+# main directory) so we can manage and switch between multiple different WRK_DIR
+# versions as required.
+
+# Allow this to be overridable or use the default.
+[ -z $TOOLSET_ROOT_DIR ] && TOOLSET_ROOT_DIR=/Users/Shared/work || true
+
+if [ $(mkdir -p $TOOLSET_ROOT_DIR 2>/dev/null; echo $?) -eq 0 ] &&
+ [ -w $TOOLSET_ROOT_DIR ] ; then
+ : # nothing to do, everything ok
+else
+ echo "❌ directory not usable (TOOLSET_ROOT_DIR): $TOOLSET_ROOT_DIR"
+ exit 1
+fi
+
+### toolset directories ########################################################
+
+# This is where .dmg files with pre-compiled toolsets are downloaded to.
+TOOLSET_REPO_DIR=$TOOLSET_ROOT_DIR/repo
+# Persistent location for ccache.
+export CCACHE_DIR=$TOOLSET_ROOT_DIR/ccache
+
+### work directory and subdirectories ##########################################
+
+# Allow this to be overrideable or use version number as default.
+[ -z $WRK_DIR ] && WRK_DIR=$TOOLSET_ROOT_DIR/$TOOLSET_VERSION || true
+
+OPT_DIR=$WRK_DIR/opt
+BIN_DIR=$OPT_DIR/bin
+INC_DIR=$OPT_DIR/include
+LIB_DIR=$OPT_DIR/lib
+SRC_DIR=$OPT_DIR/src
+TMP_DIR=$OPT_DIR/tmp
+
+### use TMP_DIR for everything temporary #######################################
+
+export TMP=$TMP_DIR
+export TEMP=$TMP_DIR
+export TMPDIR=$TMP_DIR
+export XDG_CACHE_HOME=$TMP_DIR # instead ~/.cache
+export XDG_CONFIG_HOME=$TMP_DIR # instead ~/.config
+export PIP_CACHE_DIR=$TMP_DIR # instead ~/Library/Caches/pip
+export PIPENV_CACHE_DIR=$TMP_DIR # instead ~/Library/Caches/pipenv
+
+# TODO: ~/Library/Caches/pip-tools ?
+
+### JHBuild subdirectories and configuration ###################################
+
+export DEVROOT=$WRK_DIR/gtk-osx
+export DEVPREFIX=$DEVROOT/local
+export DEV_SRC_ROOT=$DEVROOT/source
+
+export JHBUILDRC=$DEVROOT/jhbuildrc # requires modified gtk-osx-setup.sh
+export JHBUILDRC_CUSTOM=$JHBUILDRC-custom
+
+### Inkscape Git repository directory ##########################################
+
+# Location is different when running as GitLab CI job.
+
+if [ -z $CI_JOB_ID ]; then
+ INK_DIR=$SRC_DIR/inkscape
+else
+ INK_DIR=$SELF_DIR/../.. # SELF_DIR needs to be set by the sourcing script
+ INK_DIR=$(cd $INK_DIR; pwd -P) # make path canoncial
+fi
+
+### artifact path ##############################################################
+
+# This is the location where the final product - like application bundle or
+# diskimage (no intermediate programs/libraries/...) - is created in.
+
+ARTIFACT_DIR=$WRK_DIR/artifacts
+
+### application bundle paths ###################################################
+
+APP_DIR=$ARTIFACT_DIR/Inkscape.app
+APP_CON_DIR=$APP_DIR/Contents
+APP_RES_DIR=$APP_CON_DIR/Resources
+APP_FRA_DIR=$APP_CON_DIR/Frameworks
+APP_BIN_DIR=$APP_RES_DIR/bin
+APP_ETC_DIR=$APP_RES_DIR/etc
+APP_EXE_DIR=$APP_CON_DIR/MacOS
+APP_LIB_DIR=$APP_RES_DIR/lib
+
+APP_PLIST=$APP_CON_DIR/Info.plist
+
+### bundled Python version #####################################################
+
+PY3_MAJOR=3
+PY3_MINOR=8
+PY3_PATCH=5
+PY3_BUILD=2 # custom framework build number
+
+### download URLs for dependencies #############################################
+
+# https://github.com/dehesselle/gtk-osx
+# Forked from https://gitlab.gnome.org/GNOME/gtk-osx
+URL_GTK_OSX=https://raw.githubusercontent.com/dehesselle/gtk-osx/inkscape-1.0.x-10
+URL_GTK_OSX_SETUP=$URL_GTK_OSX/gtk-osx-setup.sh
+URL_GTK_OSX_MODULESET=$URL_GTK_OSX/modulesets-stable/gtk-osx.modules
+
+### download URLs for auxiliary software #######################################
+
+# These are versioned URLs of software that is not a direct dependency but
+# required for building, packaging or similar.
+
+# create application bundle
+# https://github.com/dehesselle/gtk-mac-bundler
+# Forked from https://gitlab.gnome.org/GNOME/gtk-mac-bundler
+URL_GTK_MAC_BUNDLER=https://github.com/dehesselle/gtk-mac-bundler/archive/f96a9daf2236814af7ace7a2fa91bbfb4f077779.tar.gz
+# Inkscapge Git repo (for standalone/non-CI builds)
+URL_INKSCAPE=https://gitlab.com/inkscape/inkscape
+# disk image icon
+URL_INKSCAPE_DMG_ICNS=https://github.com/dehesselle/mibap/raw/master/inkscape_dmg.icns
+# convert PNG image to iconset in ICNS format
+# https://github.com/bitboss-ca/png2icns
+URL_PNG2ICNS=https://github.com/bitboss-ca/png2icns/archive/v0.1.tar.gz
+# This is a relocatable Python.framework to be bundled with the app.
+# https://github.com/dehesselle/py3framework
+URL_PYTHON=https://github.com/dehesselle/py3framework/releases/download/py$PY3_MAJOR$PY3_MINOR$PY3_PATCH.$PY3_BUILD/py$PY3_MAJOR$PY3_MINOR${PY3_PATCH}_framework_$PY3_BUILD.tar.xz
+# A pre-compiled version of the whole toolset.
+# https://github.com/dehesselle/mibap
+URL_TOOLSET=https://github.com/dehesselle/mibap/releases/download/v$TOOLSET_VERSION/mibap_v$TOOLSET_VERSION.dmg
+
+### Python: packages for Inkscape ##############################################
+
+# The following Python packages are bundled with Inkscape.
+
+# https://cairocffi.readthedocs.io/en/stable/
+# https://github.com/Kozea/cairocffi
+PYTHON_CAIROCFFI=cairocffi==1.1.0
+# https://lxml.de
+# https://github.com/lxml/lxml
+# https://github.com/dehesselle/py3framework
+PYTHON_LXML=$(dirname $URL_PYTHON)/lxml-4.5.2-cp$PY3_MAJOR$PY3_MINOR-cp$PY3_MAJOR$PY3_MINOR-macosx_10_9_x86_64.whl
+# https://github.com/numpy/numpy
+PYTHON_NUMPY=numpy==1.19.1
+# https://pygobject.readthedocs.io/en/latest/
+PYTHON_PYGOBJECT=PyGObject==3.36.1
+# https://github.com/scour-project/scour
+PYTHON_SCOUR=scour==0.37
+# https://pyserial.readthedocs.io/en/latest/
+# https://github.com/pyserial/pyserial
+PYTHON_PYSERIAL=pyserial==3.4
+
+### Python: auxiliary packages #################################################
+
+# convert SVG to PNG
+# https://cairosvg.org
+PYTHON_CAIROSVG=cairosvg==2.4.2
+# create DMG
+# https://dmgbuild.readthedocs.io/en/latest/
+# https://github.com/al45tair/dmgbuild
+PYTHON_DMGBUILD="biplist==1.0.3 dmgbuild==1.4.2"
+
+### path #######################################################################
+
+export PATH=$DEVPREFIX/bin:$BIN_DIR:/usr/bin:/bin:/usr/sbin:/sbin
diff --git a/packaging/macos/030-funcs.sh b/packaging/macos/030-funcs.sh
new file mode 100644
index 0000000..3b08141
--- /dev/null
+++ b/packaging/macos/030-funcs.sh
@@ -0,0 +1,406 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# This file is part of the build pipeline for Inkscape on macOS.
+#
+# ### 030-funcs.sh ###
+# This file contains all the functions used by the other scripts. It helps
+# modularizing functionality and keeping the scripts that do the real work
+# as readable as possible.
+# This file does not include the "vars" files it requires itself (on purpose,
+# for flexibility reasons), the script that wants to use these functions
+# needs to do that. The suggested way is to always source all the "0nn-*.sh"
+# files in order.
+
+[ -z $FUNCS_INCLUDED ] && FUNCS_INCLUDED=true || return # include guard
+
+### get repository version string ##############################################
+
+function get_repo_version
+{
+ local repo=$1
+ # do it the same way as in CMakeScripts/inkscape-version.cmake
+ echo $(git -C $repo rev-parse --short HEAD)
+}
+
+### get Inkscape version from CMakeLists.txt ###################################
+
+function get_inkscape_version
+{
+ local file=$INK_DIR/CMakeLists.txt
+ local ver_major=$(grep INKSCAPE_VERSION_MAJOR $file | head -n 1 | awk '{ print $2+0 }')
+ local ver_minor=$(grep INKSCAPE_VERSION_MINOR $file | head -n 1 | awk '{ print $2+0 }')
+ local ver_patch=$(grep INKSCAPE_VERSION_PATCH $file | head -n 1 | awk '{ print $2+0 }')
+ local ver_suffix=$(grep INKSCAPE_VERSION_SUFFIX $file | head -n 1 | awk '{ print $2 }')
+
+ ver_suffix=${ver_suffix%\"*} # remove "double quote and everything after" from end
+ ver_suffix=${ver_suffix#\"} # remove "double quote" from beginning
+
+ echo $ver_major.$ver_minor.$ver_patch$ver_suffix
+}
+
+### get compression flag by filename extension #################################
+
+function get_comp_flag
+{
+ local file=$1
+
+ local extension=${file##*.}
+
+ case $extension in
+ bz2) echo "j" ;;
+ gz) echo "z" ;;
+ tgz) echo "z" ;;
+ xz) echo "J" ;;
+ *) echo "ERROR unknown extension $extension"
+ esac
+}
+
+### download and extract source tarball ########################################
+
+function install_source
+{
+ local url=$1
+ local target_dir=$2 # optional: target directory, defaults to $SRC_DIR
+ local options=$3 # optional: additional options for 'tar'
+
+ [ ! -d $TMP_DIR ] && mkdir -p $TMP_DIR
+ local log=$(mktemp $TMP_DIR/$FUNCNAME.XXXX)
+ [ -z $target_dir ] && target_dir=$SRC_DIR
+ [ ! -d $SRC_DIR ] && mkdir -p $SRC_DIR
+
+ cd $target_dir
+
+ # This downloads a file and pipes it directly into tar (file is not saved
+ # to disk) to extract it. Output from stderr is saved temporarily to
+ # determine the directory the files have been extracted to.
+ curl -L $(preferCachedUrl $url) | tar xv$(get_comp_flag $url) $options 2>$log
+ cd $(head -1 $log | awk '{ print $2 }')
+ [ $? -eq 0 ] && rm $log || echo "$FUNCNAME: check $log"
+}
+
+### download a file and save to disk ###########################################
+
+function save_file
+{
+ local url=$1
+ local target_dir=$2 # optional argument, defaults to $SRC_DIR
+
+ [ -z $target_dir ] && target_dir=$SRC_DIR
+
+ local file=$target_dir/$(basename $url)
+ if [ -f $file ]; then
+ echo "$FUNCNAME: file $file exists"
+ else
+ curl -o $file -L $url
+ fi
+}
+
+### make, make install in jhbuild environment ##################################
+
+function make_makeinstall
+{
+ jhbuild run make
+ jhbuild run make install
+}
+
+### configure, make, make install in jhbuild environment #######################
+
+function configure_make_makeinstall
+{
+ local flags="$*"
+
+ jhbuild run ./configure --prefix=$OPT_DIR $flags
+ make_makeinstall
+}
+
+### cmake, make, make install in jhbuild environment ###########################
+
+function cmake_make_makeinstall
+{
+ local flags="$*"
+
+ mkdir builddir
+ cd builddir
+ jhbuild run cmake -DCMAKE_INSTALL_PREFIX=$OPT_DIR $flags ..
+ make_makeinstall
+}
+
+### create and mount ramdisk ###################################################
+
+# There is a more common approach to do this using
+# diskutil eraseVolume HFS+ VolName $(hdiutil attach -nomount ram://<size>)
+# but that always attaches the ramdisk below '/Volumes'.
+# To have full control, we need to do it as follows.
+
+function create_ramdisk
+{
+ local dir=$1 # mountpoint
+ local size=$2 # unit is GiB
+
+ if [ $(mount | grep $dir | wc -l) -eq 0 ]; then
+ local device=$(hdiutil attach -nomount ram://$(expr $size \* 1024 \* 2048))
+ newfs_hfs -v "RAMDISK" $device
+ mount -o noatime,nobrowse -t hfs $device $dir
+ fi
+}
+
+### insert line into a textfile ################################################
+
+# usage:
+# insert_before <filename> <insert before this pattern> <line to insert>
+
+function insert_before
+{
+ local file=$1
+ local pattern=$2
+ local line=$3
+
+ local file_tmp=$(mktemp)
+ awk "/${pattern}/{print \"$line\"}1" $file > $file_tmp
+ cat $file_tmp > $file # we don't 'mv' to preserve permissions
+ rm $file_tmp
+}
+
+### escape replacement string for sed ##########################################
+
+# Escape slashes, backslashes and ampersands in strings to be used s as
+# replacement strings when using 'sed'. Newlines are not taken into
+# consideartion here.
+# reference: https://stackoverflow.com/a/2705678
+
+function escape_sed
+{
+ local string="$*"
+
+ echo "$string" | sed -e 's/[\/&]/\\&/g'
+}
+
+### replace line that matches pattern ##########################################
+
+function replace_line
+{
+ local file=$1
+ local pattern=$2
+ local replacement=$3
+
+ sed -i '' "s/.*${pattern}.*/$(escape_sed $replacement)/" $file
+}
+
+### relocate a library dependency ##############################################
+
+function relocate_dependency
+{
+ local target=$1 # fully qualified path and library name to new location
+ local library=$2 # library to be modified (change 'source' to 'target'I
+
+ local source_lib=${target##*/} # get library filename from target location
+ local source=$(otool -L $library | grep $source_lib | awk '{ print $1 }')
+
+ install_name_tool -change $source $target $library
+}
+
+### relocate all neighbouring libraries in a directory #########################
+
+function relocate_neighbouring_libs
+{
+ local dir=$1
+
+ for lib in $dir/*.dylib; do
+ for linked_lib in $(otool -L $lib | tail -n +3 | awk '{ print $1 }'); do
+ if [ -f $APP_LIB_DIR/$(basename $linked_lib) ]; then
+ relocate_dependency @loader_path/$(basename $linked_lib) $lib
+ fi
+ done
+ done
+}
+
+### 'readlink -f' replacement ##################################################
+
+# This is what the oneliner setting SELF_DIR (see top of file) is based on.
+
+function readlinkf
+{
+ # 'readlink -f' replacement: https://stackoverflow.com/a/1116890
+ # 'do while' replacement: https://stackoverflow.com/a/16491478
+
+ local file=$1
+
+ # iterate down a (possible) chain of symlinks
+ while
+ [ ! -z $(readlink $file) ] && file=$(readlink $file)
+ cd $(dirname $file)
+ file=$(basename $file)
+ [ -L "$file" ]
+ do
+ :
+ done
+
+ # Compute the canonicalized name by finding the physical path
+ # for the directory we're in and appending the target file.
+ echo $(pwd -P)/$file
+}
+
+### create disk image ##########################################################
+
+function create_dmg
+{
+ local app=$1
+ local dmg=$2
+ local cfg=$3
+
+ # set application
+ sed -i '' "s/PLACEHOLDERAPPLICATION/$(escape_sed $app)/" $cfg
+
+ # set disk image icon (if it exists)
+ local icon=$SRC_DIR/$(basename -s .py $cfg).icns
+ if [ -f $icon ]; then
+ sed -i '' "s/PLACEHOLDERICON/$(escape_sed $icon)/" $cfg
+ fi
+
+ # set background image (if it exists)
+ local background=$SRC_DIR/$(basename -s .py $cfg).png
+ if [ -f $background ]; then
+ sed -i '' "s/PLACEHOLDERBACKGROUND/$(escape_sed $background)/" $cfg
+ fi
+
+ # create disk image
+ dmgbuild -s $cfg "$(basename -s .app $app)" $dmg
+}
+
+### run script via Terminal.app ################################################
+
+# This is for commands that otherwise don't behave when running in CI.
+# It requires a running desktop session (i.e. logged in user) and special
+# permissions on newer macOS versions.
+# Adapted from: https://stackoverflow.com/a/27970527
+# https://gist.github.com/masci/ff51d9cf40a87a80094c
+
+function run_in_terminal
+{
+ local command=$1
+
+ #local window_id=$(uuidgen) # this would be really unique but...
+ local window_id=$(date +%s) # ...seconds would help more with debugging
+
+ osascript <<EOF
+tell application "Terminal"
+ set _tab to do script "echo -n -e \"\\\033]0;$window_id\\\007\"; $command; exit"
+ delay 1
+ repeat while _tab is busy
+ delay 1
+ end repeat
+ close (every window whose name contains "$window_id")
+end tell
+EOF
+}
+
+### replacements for plain echo: ok, error, info, warning ######################
+
+function echo_ok
+{
+ echo -e "✅ $*"
+}
+
+function echo_err
+{
+ echo -e "❌ $*"
+}
+
+function echo_warn
+{
+ echo -e "⚠️ $*"
+}
+
+function echo_info
+{
+ echo -e "ℹ️ $*"
+}
+
+function echo_act # action
+{
+ echo -e "➡️ $*"
+}
+
+### create a ramdisk and return the device #####################################
+
+function create_ram_device
+{
+ local size_gib=$1 # unit is GiB
+ local name=$2 # volume name
+
+ [ -z $name ] && name=$(uuidgen | md5 | head -c 8) # generate random name
+
+ local size_sectors=$(expr $size_gib \* 1024 \* 2048)
+ local device=$(hdiutil attach -nomount ram://$size_sectors)
+ newfs_hfs -v "$name" $device >/dev/null
+
+ echo $device
+}
+
+### attach a disk image and return the device ##################################
+
+function create_dmg_device
+{
+ local dmg=$1
+ local options=$2 # optional arguments for hdiutil
+
+ local device=$(hdiutil attach -nomount $dmg $options | grep "^/dev/disk" | \
+ grep "Apple_HFS" | awk '{ print $1 }')
+
+ echo $device
+}
+
+### replace URL ################################################################
+
+function preferCachedUrl
+{
+ local url=$1
+
+ # This is a placeholder function you can use to replace URLs with locally
+ # mirrored ones.
+
+ echo $url
+}
+
+### install Python package with Python.framework ###############################
+
+function pip_install
+{
+ local package=$1
+
+ local PATH_ORIGINAL=$PATH
+ export PATH=$APP_FRA_DIR/Python.framework/Versions/Current/bin:$PATH
+
+ pip$PY3_MAJOR install \
+ --prefix=$APP_RES_DIR \
+ --ignore-installed \
+ $package
+
+ export PATH=$PATH_ORIGINAL
+}
+
+### this function is called on "trap ERR" ######################################
+
+# macOS' old bash 3.x has a bug that causes the line number of the error
+# to point to the function entry instead of the command inside the function.
+
+function catch_error
+{
+ local file=$1
+ local line=$2
+ local func=$3
+ local command=$4
+ local rc=$5
+
+ [ -z $func ] && func="main" || true
+
+ ((ERRTRACE_COUNT++))
+
+ case $ERRTRACE_COUNT in
+ 1) echo -e "\033[97m\033[101m\033[1m[$file:$line] $func failed with rc=$rc\033[39m\033[49m\033[0m"
+ echo -e "\033[93m$command\033[39m"
+ ;;
+ *) echo -e "[$file:$line] <- $func"
+ ;;
+ esac
+}
diff --git a/packaging/macos/110-sysprep.sh b/packaging/macos/110-sysprep.sh
new file mode 100755
index 0000000..56bc5eb
--- /dev/null
+++ b/packaging/macos/110-sysprep.sh
@@ -0,0 +1,40 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# This file is part of the build pipeline for Inkscape on macOS.
+#
+# ### 110-sysprep.sh ###
+# System preparation tasks.
+
+### load settings and functions ################################################
+
+SELF_DIR=$(F=$0; while [ ! -z $(readlink $F) ] && F=$(readlink $F); cd $(dirname $F); F=$(basename $F); [ -L $F ]; do :; done; echo $(pwd -P))
+for script in $SELF_DIR/0??-*.sh; do source $script; done
+
+### initial information ########################################################
+
+echo_info "TOOLSET_ROOT_DIR = $TOOLSET_ROOT_DIR"
+echo_info "WRK_DIR = $WRK_DIR"
+
+### check for presence of SDK ##################################################
+
+if [ ! -d $SDKROOT ]; then
+ echo_err "SDK not found: $SDKROOT"
+ exit 1
+fi
+
+### create work directory ######################################################
+
+[ ! -d $WRK_DIR ] && mkdir -p $WRK_DIR || true
+
+### create temporary directory #################################################
+
+[ ! -d $TMP_DIR ] && mkdir -p $TMP_DIR || true
+
+### create binary directory #################################################
+
+[ ! -d $BIN_DIR ] && mkdir -p $BIN_DIR || true
+
+### create toolset repository directory ########################################
+
+[ ! -d $TOOLSET_REPO_DIR ] && mkdir -p $TOOLSET_REPO_DIR || true
diff --git a/packaging/macos/130-jhbuild-bootstrap.sh b/packaging/macos/130-jhbuild-bootstrap.sh
new file mode 100755
index 0000000..d6611bc
--- /dev/null
+++ b/packaging/macos/130-jhbuild-bootstrap.sh
@@ -0,0 +1,69 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# This file is part of the build pipeline for Inkscape on macOS.
+#
+# ### 130-jhbuild-bootstrap.sh ###
+# Bootstrap the JHBuild environment.
+
+### load global settings and functions #########################################
+
+SELF_DIR=$(F=$0; while [ ! -z $(readlink $F) ] && F=$(readlink $F); cd $(dirname $F); F=$(basename $F); [ -L $F ]; do :; done; echo $(pwd -P))
+for script in $SELF_DIR/0??-*.sh; do source $script; done
+
+### local settings #############################################################
+
+export PYTHONUSERBASE=$DEVPREFIX
+export PIP_CONFIG_DIR=$DEVROOT/pip
+
+### install and configure jhbuild ##############################################
+
+bash <(curl -s $URL_GTK_OSX_SETUP) # run jhbuild setup script
+
+# JHBuild: paths
+echo "checkoutroot = '$SRC_DIR/checkout'" >> $JHBUILDRC_CUSTOM
+echo "prefix = '$OPT_DIR'" >> $JHBUILDRC_CUSTOM
+echo "tarballdir = '$SRC_DIR/download'" >> $JHBUILDRC_CUSTOM
+
+# JHBuild: console output
+echo "quiet_mode = True" >> $JHBUILDRC_CUSTOM
+echo "progress_bar = True" >> $JHBUILDRC_CUSTOM
+
+# JHBuild: moduleset
+echo "moduleset = '$URL_GTK_OSX_MODULESET'" >> $JHBUILDRC_CUSTOM
+
+# JHBuild: macOS SDK
+sed -i "" "s/^setup_sdk/#setup_sdk/" $JHBUILDRC_CUSTOM
+echo "setup_sdk(target=\"$SDK_VERSION\")" >> $JHBUILDRC_CUSTOM
+echo "os.environ[\"SDKROOT\"]=\"$SDKROOT\"" >> $JHBUILDRC_CUSTOM
+
+# JHBuild: TODO: I have forgotten why this is here
+echo "if \"openssl\" in skip:" >> $JHBUILDRC_CUSTOM
+echo " skip.remove(\"openssl\")" >> $JHBUILDRC_CUSTOM
+
+# Remove harmful settings in regards to the target platform:
+# - MACOSX_DEPLOYMENT_TARGET
+# - -mmacosx-version-min
+#
+# otool -l <library> | grep -A 3 -B 1 LC_VERSION_MIN_MACOSX
+#
+# cmd LC_VERSION_MIN_MACOSX
+# cmdsize 16
+# version 10.11
+# sdk n/a < - - - notarized app won't load this library
+echo "os.environ.pop(\"MACOSX_DEPLOYMENT_TARGET\")" \
+ >> $JHBUILDRC_CUSTOM
+echo "os.environ[\"CFLAGS\"] = \"-O2 -I$SDKROOT/usr/include -isysroot $SDKROOT\"" \
+ >> $JHBUILDRC_CUSTOM
+echo "os.environ[\"CPPFLAGS\"] = \"-I$INC_DIR -I$SDKROOT/usr/include -isysroot $SDKROOT\"" \
+ >> $JHBUILDRC_CUSTOM
+echo "os.environ[\"CXXFLAGS\"] = \"-O2 -I$SDKROOT/usr/include -isysroot $SDKROOT\"" \
+ >> $JHBUILDRC_CUSTOM
+echo "os.environ[\"LDFLAGS\"] = \"-L$LIB_DIR -L$SDKROOT/usr/lib -isysroot $SDKROOT -Wl,-headerpad_max_install_names\"" \
+ >> $JHBUILDRC_CUSTOM
+echo "os.environ[\"OBJCFLAGS\"] = \"-O2 -I$SDKROOT/usr/include -isysroot $SDKROOT\"" \
+ >> $JHBUILDRC_CUSTOM
+
+### bootstrap JHBuild ##########################################################
+
+jhbuild bootstrap-gtk-osx
diff --git a/packaging/macos/140-jhbuild-gtk3.sh b/packaging/macos/140-jhbuild-gtk3.sh
new file mode 100755
index 0000000..73709f0
--- /dev/null
+++ b/packaging/macos/140-jhbuild-gtk3.sh
@@ -0,0 +1,19 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# This file is part of the build pipeline for Inkscape on macOS.
+#
+# ### 140-jhbuild-gtk3.sh ###
+# Install GTK3 libraries.
+
+### load settings and functions ################################################
+
+SELF_DIR=$(F=$0; while [ ! -z $(readlink $F) ] && F=$(readlink $F); cd $(dirname $F); F=$(basename $F); [ -L $F ]; do :; done; echo $(pwd -P))
+for script in $SELF_DIR/0??-*.sh; do source $script; done
+
+### install GTK3 libraries #####################################################
+
+jhbuild build \
+ meta-gtk-osx-bootstrap \
+ meta-gtk-osx-gtk3 \
+ meta-gtk-osx-gtkmm3
diff --git a/packaging/macos/150-jhbuild-inkdeps.sh b/packaging/macos/150-jhbuild-inkdeps.sh
new file mode 100755
index 0000000..1de75d2
--- /dev/null
+++ b/packaging/macos/150-jhbuild-inkdeps.sh
@@ -0,0 +1,37 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# This file is part of the build pipeline for Inkscape on macOS.
+#
+# ### 150-jhbuild-inkdeps.sh ###
+# Install additional dependencies into our jhbuild environment required for
+# building Inkscape.
+
+### load settings and functions ################################################
+
+SELF_DIR=$(F=$0; while [ ! -z $(readlink $F) ] && F=$(readlink $F); cd $(dirname $F); F=$(basename $F); [ -L $F ]; do :; done; echo $(pwd -P))
+for script in $SELF_DIR/0??-*.sh; do source $script; done
+
+### install Inkscape dependencies ##############################################
+
+# Part of gtk-osx module sets.
+
+jhbuild build \
+ libsoup
+
+# Part of inkscape module set.
+
+jhbuild build \
+ boehm_gc \
+ double_conversion \
+ gdl \
+ ghostscript \
+ google_test \
+ gsl \
+ gtkspell3 \
+ imagemagick \
+ libcdr \
+ openjpeg \
+ openmp \
+ poppler \
+ potrace
diff --git a/packaging/macos/160-jhbuild-other.sh b/packaging/macos/160-jhbuild-other.sh
new file mode 100755
index 0000000..cb54da7
--- /dev/null
+++ b/packaging/macos/160-jhbuild-other.sh
@@ -0,0 +1,61 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# This file is part of the build pipeline for Inkscape on macOS.
+#
+# ### 160-jhbuild-other.sh ###
+# Install additional components that are not direct dependencies, like tools
+# required for packaging.
+
+### load settings and functions ################################################
+
+SELF_DIR=$(F=$0; while [ ! -z $(readlink $F) ] && F=$(readlink $F); cd $(dirname $F); F=$(basename $F); [ -L $F ]; do :; done; echo $(pwd -P))
+for script in $SELF_DIR/0??-*.sh; do source $script; done
+
+set -e -o errtrace
+trap 'catch_error "$SELF_NAME" "$LINENO" "$FUNCNAME" "${BASH_COMMAND}" "${?}"' ERR
+
+### install disk image creator #################################################
+
+jhbuild run pip3 install $PYTHON_DMGBUILD
+
+### download icon for disk image ###############################################
+
+# dmgbuild offers to generate a badged version of the disk image icon, but
+# it doesn't work and I have not investigated yet. We use a pre-made image
+# for now.
+
+save_file $URL_INKSCAPE_DMG_ICNS
+
+### install gtk-mac-bundler ####################################################
+
+(
+ export GMB_BINDIR=$BIN_DIR
+
+ install_source $URL_GTK_MAC_BUNDLER
+ jhbuild run make install
+)
+
+### install svg to png convertor ###############################################
+
+jhbuild run pip3 install $PYTHON_CAIROSVG
+jhbuild run pip3 install $PYTHON_CAIROCFFI
+
+### install png to icns converter ##############################################
+
+install_source $URL_PNG2ICNS
+ln -s $(pwd)/png2icns.sh $BIN_DIR
+
+### downlaod a pre-built Python.framework ######################################
+
+# This will be bundled with the application.
+
+save_file $URL_PYTHON
+
+### install compiler cache #####################################################
+
+jhbuild build ccache
+
+################################################################################
+
+# vim: expandtab:shiftwidth=2:tabstop=2:softtabstop=2 :
diff --git a/packaging/macos/210-inkscape-build.sh b/packaging/macos/210-inkscape-build.sh
new file mode 100755
index 0000000..5f6f61e
--- /dev/null
+++ b/packaging/macos/210-inkscape-build.sh
@@ -0,0 +1,59 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# This file is part of the build pipeline for Inkscape on macOS.
+#
+# ### 210-inkscape-build.sh ###
+# Build Inscape.
+
+### load settings and functions ################################################
+
+SELF_DIR=$(F=$0; while [ ! -z $(readlink $F) ] && F=$(readlink $F); cd $(dirname $F); F=$(basename $F); [ -L $F ]; do :; done; echo $(pwd -P))
+for script in $SELF_DIR/0??-*.sh; do source $script; done
+
+set -e -o errtrace
+trap 'catch_error "$SELF_NAME" "$LINENO" "$FUNCNAME" "${BASH_COMMAND}" "${?}"' ERR
+
+### build Inkscape #############################################################
+
+if [ -z $CI_JOB_ID ]; then # running standalone
+ git clone --recurse-submodules --depth 10 $URL_INKSCAPE $INK_DIR
+fi
+
+[ -d $INK_DIR.build ] && rm -rf $INK_DIR.build || true # cleanup previous run
+
+mkdir -p $INK_DIR.build
+cd $INK_DIR.build
+
+# about the '-DOpenMP_*' arguments:
+# All the settings for OpenMP are to trigger the detection during 'cmake'.
+# Experimenting with a "Hello World"-style example shows that linking with
+# '-lomp' would suffice, no '-fopenmp' required.
+# TODO further investigation into required flags
+
+cmake \
+ -DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
+ -DCMAKE_PREFIX_PATH=$OPT_DIR \
+ -DCMAKE_INSTALL_PREFIX=$OPT_DIR \
+ -DOpenMP_CXX_FLAGS="-Xclang -fopenmp" \
+ -DOpenMP_C_FLAGS="-Xclang -fopenmp" \
+ -DOpenMP_CXX_LIB_NAMES="omp" \
+ -DOpenMP_C_LIB_NAMES="omp" \
+ -DOpenMP_omp_LIBRARY=$LIB_DIR/libomp.dylib \
+ $INK_DIR
+
+make
+make install
+make tests
+
+### patch Poppler library locations ############################################
+
+relocate_dependency $LIB_DIR/libpoppler.94.dylib $BIN_DIR/inkscape
+relocate_dependency $LIB_DIR/libpoppler-glib.8.dylib $BIN_DIR/inkscape
+relocate_dependency $LIB_DIR/libpoppler.94.dylib $LIB_DIR/inkscape/libinkscape_base.dylib
+relocate_dependency $LIB_DIR/libpoppler-glib.8.dylib $LIB_DIR/inkscape/libinkscape_base.dylib
+
+### patch OpenMP library locations #############################################
+
+relocate_dependency $LIB_DIR/libomp.dylib $BIN_DIR/inkscape
+relocate_dependency $LIB_DIR/libomp.dylib $LIB_DIR/inkscape/libinkscape_base.dylib
diff --git a/packaging/macos/220-inkscape-package.sh b/packaging/macos/220-inkscape-package.sh
new file mode 100755
index 0000000..903bcf9
--- /dev/null
+++ b/packaging/macos/220-inkscape-package.sh
@@ -0,0 +1,175 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# This file is part of the build pipeline for Inkscape on macOS.
+#
+# ### 220-inkscape-package.sh ###
+# Create Inkscape application bundle.
+
+### load settings and functions ################################################
+
+SELF_DIR=$(F=$0; while [ ! -z $(readlink $F) ] && F=$(readlink $F); cd $(dirname $F); F=$(basename $F); [ -L $F ]; do :; done; echo $(pwd -P))
+for script in $SELF_DIR/0??-*.sh; do source $script; done
+
+set -e -o errtrace
+trap 'catch_error "$SELF_NAME" "$LINENO" "$FUNCNAME" "${BASH_COMMAND}" "${?}"' ERR
+
+### create application bundle ##################################################
+
+mkdir -p $ARTIFACT_DIR
+
+( # use subshell to fence temporary variables
+
+ BUILD_DIR=$SRC_DIR/gtk-mac-bundler.build
+ mkdir -p $BUILD_DIR
+
+ cp $SELF_DIR/inkscape.bundle $BUILD_DIR
+ cp $SELF_DIR/inkscape.plist $BUILD_DIR
+
+ export ARTIFACT_DIR # referenced in inkscape.bundle
+ cd $BUILD_DIR
+ jhbuild run gtk-mac-bundler inkscape.bundle
+)
+
+# Rename to get from lowercase to capitalized "i". This works only on
+# case-insensitive filesystems (default on macOS).
+
+mv $APP_DIR $APP_DIR.tmp
+mv $APP_DIR.tmp $APP_DIR
+
+# Patch library link paths.
+
+relocate_dependency @executable_path/../Resources/lib/inkscape/libinkscape_base.dylib $APP_EXE_DIR/inkscape
+
+relocate_dependency @loader_path/../libpoppler.94.dylib $APP_LIB_DIR/inkscape/libinkscape_base.dylib
+relocate_dependency @loader_path/../libpoppler-glib.8.dylib $APP_LIB_DIR/inkscape/libinkscape_base.dylib
+
+relocate_neighbouring_libs $APP_LIB_DIR
+
+# update Inkscape version information
+/usr/libexec/PlistBuddy -c "Set CFBundleShortVersionString '$(get_inkscape_version) ($(get_repo_version $INK_DIR))'" $APP_PLIST
+/usr/libexec/PlistBuddy -c "Set CFBundleVersion '$(get_inkscape_version) ($(get_repo_version $INK_DIR))'" $APP_PLIST
+
+# update minimum system version according to deployment target
+/usr/libexec/PlistBuddy -c "Set LSMinimumSystemVersion '$SDK_VERSION'" $APP_PLIST
+
+### generate application icon ##################################################
+
+# svg to png
+
+(
+ export DYLD_FALLBACK_LIBRARY_PATH=$LIB_DIR
+ jhbuild run cairosvg -f png -s 8 -o $SRC_DIR/inkscape.png $INK_DIR/share/branding/inkscape.svg
+)
+
+# png to icns
+
+cd $SRC_DIR # png2icns.sh outputs to current directory
+png2icns.sh inkscape.png
+mv inkscape.icns $APP_RES_DIR
+
+### bundle Python.framework ####################################################
+
+# This section deals with bundling Python.framework into the application.
+
+mkdir $APP_FRA_DIR
+install_source file://$SRC_DIR/$(basename $URL_PYTHON) $APP_FRA_DIR --exclude="Versions/$PY3_MAJOR.$PY3_MINOR/lib/python$PY3_MAJOR.$PY3_MINOR/test/"'*'
+
+# link it to $APP_BIN_DIR so it'll be in $PATH for the app
+ln -sf ../../Frameworks/Python.framework/Versions/Current/bin/python$PY3_MAJOR $APP_BIN_DIR
+
+# create '.pth' file inside Framework to include our site-packages directory
+echo "./../../../../../../../Resources/lib/python$PY3_MAJOR.$PY3_MINOR/site-packages" > $APP_FRA_DIR/Python.framework/Versions/Current/lib/python$PY3_MAJOR.$PY3_MINOR/site-packages/inkscape.pth
+
+### install Python package: lxml ###############################################
+
+pip_install $PYTHON_LXML
+
+# patch 'etree'
+relocate_dependency @loader_path/../../../libxml2.2.dylib $APP_LIB_DIR/python$PY3_MAJOR.$PY3_MINOR/site-packages/lxml/etree.cpython-$PY3_MAJOR${PY3_MINOR}-darwin.so
+relocate_dependency @loader_path/../../../libz.1.dylib $APP_LIB_DIR/python$PY3_MAJOR.$PY3_MINOR/site-packages/lxml/etree.cpython-$PY3_MAJOR${PY3_MINOR}-darwin.so
+relocate_dependency @loader_path/../../../libxslt.1.dylib $APP_LIB_DIR/python$PY3_MAJOR.$PY3_MINOR/site-packages/lxml/etree.cpython-$PY3_MAJOR${PY3_MINOR}-darwin.so
+relocate_dependency @loader_path/../../../libexslt.0.dylib $APP_LIB_DIR/python$PY3_MAJOR.$PY3_MINOR/site-packages/lxml/etree.cpython-$PY3_MAJOR${PY3_MINOR}-darwin.so
+# patch 'objectify'
+relocate_dependency @loader_path/../../../libxml2.2.dylib $APP_LIB_DIR/python$PY3_MAJOR.$PY3_MINOR/site-packages/lxml/objectify.cpython-$PY3_MAJOR${PY3_MINOR}-darwin.so
+relocate_dependency @loader_path/../../../libz.1.dylib $APP_LIB_DIR/python$PY3_MAJOR.$PY3_MINOR/site-packages/lxml/objectify.cpython-$PY3_MAJOR${PY3_MINOR}-darwin.so
+relocate_dependency @loader_path/../../../libxslt.1.dylib $APP_LIB_DIR/python$PY3_MAJOR.$PY3_MINOR/site-packages/lxml/objectify.cpython-$PY3_MAJOR${PY3_MINOR}-darwin.so
+relocate_dependency @loader_path/../../../libexslt.0.dylib $APP_LIB_DIR/python$PY3_MAJOR.$PY3_MINOR/site-packages/lxml/objectify.cpython-$PY3_MAJOR${PY3_MINOR}-darwin.so
+
+### install Python package: NumPy ##############################################
+
+pip_install $PYTHON_NUMPY
+
+### install Python package: PyGObject ##########################################
+
+pip_install $PYTHON_PYGOBJECT
+
+# patch '_gi'
+relocate_dependency @loader_path/../../../libglib-2.0.0.dylib $APP_LIB_DIR/python$PY3_MAJOR.$PY3_MINOR/site-packages/gi/_gi.cpython-$PY3_MAJOR${PY3_MINOR}-darwin.so
+relocate_dependency @loader_path/../../../libintl.8.dylib $APP_LIB_DIR/python$PY3_MAJOR.$PY3_MINOR/site-packages/gi/_gi.cpython-$PY3_MAJOR${PY3_MINOR}-darwin.so
+relocate_dependency @loader_path/../../../libgio-2.0.0.dylib $APP_LIB_DIR/python$PY3_MAJOR.$PY3_MINOR/site-packages/gi/_gi.cpython-$PY3_MAJOR${PY3_MINOR}-darwin.so
+relocate_dependency @loader_path/../../../libgobject-2.0.0.dylib $APP_LIB_DIR/python$PY3_MAJOR.$PY3_MINOR/site-packages/gi/_gi.cpython-$PY3_MAJOR${PY3_MINOR}-darwin.so
+relocate_dependency @loader_path/../../../libgirepository-1.0.1.dylib $APP_LIB_DIR/python$PY3_MAJOR.$PY3_MINOR/site-packages/gi/_gi.cpython-$PY3_MAJOR${PY3_MINOR}-darwin.so
+relocate_dependency @loader_path/../../../libffi.7.dylib $APP_LIB_DIR/python$PY3_MAJOR.$PY3_MINOR/site-packages/gi/_gi.cpython-$PY3_MAJOR${PY3_MINOR}-darwin.so
+
+# patch '_gi_cairo'
+relocate_dependency @loader_path/../../../libglib-2.0.0.dylib $APP_LIB_DIR/python$PY3_MAJOR.$PY3_MINOR/site-packages/gi/_gi_cairo.cpython-$PY3_MAJOR${PY3_MINOR}-darwin.so
+relocate_dependency @loader_path/../../../libintl.8.dylib $APP_LIB_DIR/python$PY3_MAJOR.$PY3_MINOR/site-packages/gi/_gi_cairo.cpython-$PY3_MAJOR${PY3_MINOR}-darwin.so
+relocate_dependency @loader_path/../../../libgio-2.0.0.dylib $APP_LIB_DIR/python$PY3_MAJOR.$PY3_MINOR/site-packages/gi/_gi_cairo.cpython-$PY3_MAJOR${PY3_MINOR}-darwin.so
+relocate_dependency @loader_path/../../../libgobject-2.0.0.dylib $APP_LIB_DIR/python$PY3_MAJOR.$PY3_MINOR/site-packages/gi/_gi_cairo.cpython-$PY3_MAJOR${PY3_MINOR}-darwin.so
+relocate_dependency @loader_path/../../../libgirepository-1.0.1.dylib $APP_LIB_DIR/python$PY3_MAJOR.$PY3_MINOR/site-packages/gi/_gi_cairo.cpython-$PY3_MAJOR${PY3_MINOR}-darwin.so
+relocate_dependency @loader_path/../../../libffi.7.dylib $APP_LIB_DIR/python$PY3_MAJOR.$PY3_MINOR/site-packages/gi/_gi_cairo.cpython-$PY3_MAJOR${PY3_MINOR}-darwin.so
+relocate_dependency @loader_path/../../../libcairo.2.dylib $APP_LIB_DIR/python$PY3_MAJOR.$PY3_MINOR/site-packages/gi/_gi_cairo.cpython-$PY3_MAJOR${PY3_MINOR}-darwin.so
+relocate_dependency @loader_path/../../../libcairo-gobject.2.dylib $APP_LIB_DIR/python$PY3_MAJOR.$PY3_MINOR/site-packages/gi/_gi_cairo.cpython-$PY3_MAJOR${PY3_MINOR}-darwin.so
+
+### install Python package: Pycairo ############################################
+
+# This package got pulled in by PyGObject.
+
+# patch '_cairo'
+relocate_dependency @loader_path/../../../libcairo.2.dylib $APP_LIB_DIR/python$PY3_MAJOR.$PY3_MINOR/site-packages/cairo/_cairo.cpython-$PY3_MAJOR${PY3_MINOR}-darwin.so
+
+### install Python package: pySerial ###########################################
+
+pip_install $PYTHON_PYSERIAL
+
+### install Python package: Scour ##############################################
+
+pip_install $PYTHON_SCOUR
+
+### precompile all Python packages #############################################
+
+$APP_FRA_DIR/Python.framework/Versions/Current/bin/python$PY3_MAJOR -m compileall -f $APP_DIR || true
+
+### fontconfig #################################################################
+
+# Mimic the behavior of having all files under 'share' and linking the
+# active ones to 'etc'.
+cd $APP_ETC_DIR/fonts/conf.d
+
+for file in ./*.conf; do
+ ln -sf ../../../share/fontconfig/conf.avail/$(basename $file)
+done
+
+# Our customized version loses all the non-macOS paths and sets a cache
+# directory below '$HOME/Library/Application Support/Inkscape'.
+cp $SELF_DIR/fonts.conf $APP_ETC_DIR/fonts
+
+### Ghostscript ################################################################
+
+relocate_dependency @executable_path/../lib/libfontconfig.1.dylib $APP_BIN_DIR/gs
+relocate_dependency @executable_path/../lib/libfreetype.6.dylib $APP_BIN_DIR/gs
+
+### create GObject introspection repository ####################################
+
+mkdir $APP_LIB_DIR/girepository-1.0
+
+# remove fully qualified paths from libraries in *.gir files
+for gir in $OPT_DIR/share/gir-1.0/*.gir; do
+ sed "s/$(escape_sed $LIB_DIR/)//g" $gir > $SRC_DIR/$(basename $gir)
+done
+
+# compile *.gir into *.typelib files
+for gir in $SRC_DIR/*.gir; do
+ jhbuild run g-ir-compiler -o $APP_LIB_DIR/girepository-1.0/$(basename -s .gir $gir).typelib $gir
+done
diff --git a/packaging/macos/230-inkscape-distrib.sh b/packaging/macos/230-inkscape-distrib.sh
new file mode 100755
index 0000000..f722444
--- /dev/null
+++ b/packaging/macos/230-inkscape-distrib.sh
@@ -0,0 +1,51 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# This file is part of the build pipeline for Inkscape on macOS.
+#
+# ### 230-inkscape-distrib.sh ###
+# Get application ready for distribution.
+
+### load settings and functions ################################################
+
+SELF_DIR=$(F=$0; while [ ! -z $(readlink $F) ] && F=$(readlink $F); cd $(dirname $F); F=$(basename $F); [ -L $F ]; do :; done; echo $(pwd -P))
+for script in $SELF_DIR/0??-*.sh; do source $script; done
+
+set -o errtrace
+trap 'catch_error "$SELF_NAME" "$LINENO" "$FUNCNAME" "${BASH_COMMAND}" "${?}"' ERR
+
+### create disk image for distribution #########################################
+
+# Create background for development snapshots. This is not meant for
+# official releases, those will be re-packaged manually (they also need
+# to be signed and notarized).
+
+convert -size 560x400 xc:transparent \
+ -font Andale-Mono -pointsize 64 -fill black \
+ -draw "text 20,60 'Inkscape'" \
+ -draw "text 20,120 '$(get_inkscape_version)'" \
+ -draw "text 20,180 'development'" \
+ -draw "text 20,240 'snapshot'" \
+ -draw "text 20,300 '$(get_repo_version $INK_DIR)'" \
+ $SRC_DIR/inkscape_dmg.png
+
+# create the disk image
+
+# Due to an undiagnosed instability that only occurs during CI runs (not when
+# run interactively from the terminal), the following code will be put into
+# a separate script and be executed via Terminal.app.
+# See: https://github.com/al45tair/dmgbuild/pull/11
+
+cp $SELF_DIR/inkscape_dmg.py $SRC_DIR
+
+create_dmg $ARTIFACT_DIR/Inkscape.app $TMP_DIR/Inkscape.dmg $SRC_DIR/inkscape_dmg.py
+
+rm -rf $APP_DIR
+mv $TMP_DIR/Inkscape.dmg $ARTIFACT_DIR
+
+# CI: move disk image to a location accessible for the runner
+
+if [ ! -z $CI_JOB_ID ]; then
+ [ -d $INK_DIR/artifacts ] && rm -rf $INK_DIR/artifacts
+ mv $ARTIFACT_DIR $INK_DIR/artifacts
+fi
diff --git a/packaging/macos/README.md b/packaging/macos/README.md
new file mode 100644
index 0000000..fa119c8
--- /dev/null
+++ b/packaging/macos/README.md
@@ -0,0 +1,106 @@
+# macOS build pipeline
+
+This folder contains the scripts that make up the build pipeline for Inkscape on macOS.
+
+## Usage
+
+### Requirements
+
+ℹ️ _The following is bound to change as development progresses and I won't deny that there's usually more than one way to do something, but I can only support what I use myself. So feel free to experiment and deviate, but know that __it is dangerous to go alone! Take this 🗡️.___
+
+- A __clean environment__ is key.
+ - Make sure there are no remnants from other build environments (e.g. MacPorts, Fink, Homebrew) on your system.
+ - Rule of thumb: clear out `/usr/local`.
+ - Use a dedicated user account to avoid any interference with the environment (e.g. no custom settings in `.profile`, `.bashrc`, etc.).
+
+- There are __version recommendations__.
+ - macOS Catalina 10.15.6
+ - Xcode 11.6
+ - OS X El Capitan 10.11 SDK from Xcode 7.3.1 (expected in `/opt/sdks/MacOSX10.11.sdk`)
+
+- A somewhat decent __internet connection__ for all the downloads.
+
+### Building the toolset
+
+ℹ️ _If you only want to build Inkscape and not the complete toolset, skip ahead to the next section!_
+
+1. Clone this repository and `cd` into `packaging/macos`.
+
+ ```bash
+ git clone --depth 1 https://gitlab.com/inkscape/inkscape
+ cd inkscape/packaging/macos
+ ```
+
+2. Specify a folder where all the action is going to take place. (Please avoid spaces in paths!)
+
+ ```bash
+ echo "TOOLSET_ROOT_DIR=$HOME/my_build_dir" > 015-customdir.sh
+ ```
+
+3. Build the toolset.
+
+ ```bash
+ ./build_toolset.sh
+ ```
+
+4. ☕ (Time to get a beverage - this will take a while!)
+
+### Installing a pre-compiled toolset
+
+ℹ️ _If you built the toolset yourself, skip ahead to the next section!_
+
+ℹ️ _Using `/Users/Shared/work` as `TOOLSET_ROOT_DIR` is mandatory! (This is the default.)_
+
+1. Clone this repository and `cd` into it.
+
+ ```bash
+ git clone --depth 1 https://gitlab.com/inkscape/inkscape
+ cd inkscape/packaging/macos
+ ```
+
+2. Install the toolset.
+
+ ```bash
+ ./install_toolset.sh
+ ```
+
+ This will
+
+ - download a disk image (about 1.6 GiB) to `/Users/Shared/work/repo`
+ - mount the disk image to `/Users/Shared/work/$TOOLSET_VERSION`
+ - union-mount a ramdisk (2 GiB) to `/Users/Shared/work/$TOOLSET_VERSION`
+
+ The mounted volumes won't show up in the Finder but you can see them using `diskutil`. Use `uninstall_toolset.sh` to eject them (this won't delete `repo` though).
+
+### Building Inkscape
+
+1. Build Inkscape.
+
+ ```bash
+ ./build_inkscape.sh
+ ```
+
+ Ultimately this will produce `/Users/Shared/work/$TOOLSET_VERSION/artifacts/Inkscape.dmg`.
+
+## GitLab CI
+
+Make sure the runner fulfills the same requirements as listed in the usage section above.
+
+Configure a job in your `.gitlab-ci.yml` as follows:
+
+```yaml
+buildmacos:
+ before_script:
+ - packaging/macos/install_toolset.sh
+ script:
+ - packaging/macos/build_inkscape.sh
+ after_script:
+ - packaging/macos/uninstall_toolset.sh
+ artifacts:
+ paths:
+ - artifacts/
+```
+
+## Status
+
+This is based on [mibap](https://github.com/dehesselle/mibap) and still a work in progress.
diff --git a/packaging/macos/build_inkscape.sh b/packaging/macos/build_inkscape.sh
new file mode 100755
index 0000000..dc3139a
--- /dev/null
+++ b/packaging/macos/build_inkscape.sh
@@ -0,0 +1,21 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# This file is part of the build pipeline for Inkscape on macOS.
+#
+# ### build_inkscape.sh ###
+# Compile and package Inkscape.
+
+### load settings and functions ################################################
+
+SELF_DIR=$(F=$0; while [ ! -z $(readlink $F) ] && F=$(readlink $F); \
+ cd $(dirname $F); F=$(basename $F); [ -L $F ]; do :; done; echo $(pwd -P))
+for script in $SELF_DIR/0??-*.sh; do source $script; done
+
+set -e
+
+### run scripts to compile and build Inkscape ##################################
+
+for script in $SELF_DIR/2??-*.sh; do
+ $script
+done
diff --git a/packaging/macos/build_toolset.sh b/packaging/macos/build_toolset.sh
new file mode 100755
index 0000000..5bdb5ae
--- /dev/null
+++ b/packaging/macos/build_toolset.sh
@@ -0,0 +1,44 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# This file is part of the build pipeline for Inkscape on macOS.
+#
+# ### build_toolset.sh ###
+# Create JHBuild toolset with all dependencies for Inkscape.
+
+### load settings and functions ################################################
+
+SELF_DIR=$(F=$0; while [ ! -z $(readlink $F) ] && F=$(readlink $F); \
+ cd $(dirname $F); F=$(basename $F); [ -L $F ]; do :; done; echo $(pwd -P))
+for script in $SELF_DIR/0??-*.sh; do source $script; done
+
+set -e
+
+### build toolset ##############################################################
+
+function build
+{
+ echo_info building toolset in $WRK_DIR
+ for script in $SELF_DIR/1??-*.sh; do
+ $script
+ done
+}
+
+### remove some files ##########################################################
+
+# Our way of union-mounting a writable overlay ontop of a readonly filesystem
+# introduces the additional challenge that paths cannot be written to if the
+# parent path has not been written to (either a bug or a limitation, IDK).
+# For most of the build system we work around that by re-creating the
+# complete folder structure inside the writable overlay. In some cases
+# we remove the paths causing problems.
+
+function remove_files
+{
+ rm -rf $TMP_DIR/wheels
+}
+
+### main #######################################################################
+
+build
+remove_files
diff --git a/packaging/macos/fonts.conf b/packaging/macos/fonts.conf
new file mode 100644
index 0000000..364bd3b
--- /dev/null
+++ b/packaging/macos/fonts.conf
@@ -0,0 +1,77 @@
+<?xml version="1.0"?>
+<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
+<!-- /etc/fonts/fonts.conf file to configure system font access -->
+<fontconfig>
+ <its:rules xmlns:its="http://www.w3.org/2005/11/its" version="1.0">
+ <its:translateRule translate="no" selector="/fontconfig/*[not(self::description)]"/>
+ </its:rules>
+
+ <description>Default configuration file</description>
+
+<!--
+ Usually you'll find the "DO NOT EDIT THIS FILE" warning here.
+ I removed it since I clearly edited this file. I removed the
+ non-macOS paths and configured the cachedir.
+
+ Rene de Hesselle
+-->
+
+<!-- Font directory list -->
+
+ <dir>/System/Library/Fonts</dir> <dir>/Library/Fonts</dir> <dir>~/Library/Fonts</dir>
+<!--
+ Accept deprecated 'mono' alias, replacing it with 'monospace'
+-->
+ <match target="pattern">
+ <test qual="any" name="family">
+ <string>mono</string>
+ </test>
+ <edit name="family" mode="assign" binding="same">
+ <string>monospace</string>
+ </edit>
+ </match>
+
+<!--
+ Accept alternate 'sans serif' spelling, replacing it with 'sans-serif'
+-->
+ <match target="pattern">
+ <test qual="any" name="family">
+ <string>sans serif</string>
+ </test>
+ <edit name="family" mode="assign" binding="same">
+ <string>sans-serif</string>
+ </edit>
+ </match>
+
+<!--
+ Accept deprecated 'sans' alias, replacing it with 'sans-serif'
+-->
+ <match target="pattern">
+ <test qual="any" name="family">
+ <string>sans</string>
+ </test>
+ <edit name="family" mode="assign" binding="same">
+ <string>sans-serif</string>
+ </edit>
+ </match>
+
+<!--
+ Load local system customization file
+-->
+ <include ignore_missing="yes">conf.d</include>
+
+<!-- Font cache directory list -->
+
+ <cachedir>~/Library/Application Support/Inkscape/cache/fontconfig</cachedir>
+
+ <config>
+<!--
+ Rescan configuration every 30 seconds when FcFontSetList is called
+ -->
+ <rescan>
+ <int>30</int>
+ </rescan>
+ </config>
+
+</fontconfig>
+
diff --git a/packaging/macos/inkscape.bundle b/packaging/macos/inkscape.bundle
new file mode 100644
index 0000000..5cc3752
--- /dev/null
+++ b/packaging/macos/inkscape.bundle
@@ -0,0 +1,165 @@
+<?xml version="1.0" standalone="no"?> <!--*- mode: xml -*-->
+<app-bundle>
+
+ <meta>
+ <!-- Where to pick up the GTK+ installation, icon themes,
+ etc. Note that "${env:JHBUILD_PREFIX}" is evaluated to the
+ value of the environment variable JHBUILD_PREFIX. You can
+ define additional prefixes and refer to them in paths
+ throughout this file on the form "${prefix:name}". This is
+ useful for installing certain libraries or even the
+ application itself separately. Note that JHBUILD_PREFIX is
+ defined by jhbuild, so it you are not using jhbuild you can
+ either define your own or just hardcode the path here.
+ -->
+ <prefix name="default">${env:JHBUILD_PREFIX}</prefix>
+
+ <!-- The project directory is the default location of the created
+ app. If you leave out the path, the current directory is
+ used. Note the usage of an environment variable here again.
+ -->
+ <destination overwrite="yes">${env:ARTIFACT_DIR}</destination>
+
+ <image>
+ <!-- Not implemented yet (DMG image). -->
+ </image>
+
+ <!-- Comment this out to keep the install names in binaries -->
+ <run-install-name-tool/>
+
+ <!-- Optionally specify a launcher script to use. If the
+ application sets up everything needed itself, like
+ environment variable, linker paths, etc, a launcher script is
+ not needed. If the source path is left out, the default
+ script will be used.
+ <launcher-script>${project}/gtk3-launcher.sh</launcher-script >
+ -->
+
+ <!-- Not implemented: Optional runtime, could be python or mono
+ for example.
+ -->
+ <!-- runtime copy="yes">/usr/bin/python</runtime -->
+ <!-- Indicate the active gtk version to use. This is needed only
+ for gtk+-3.0 projects. -->
+ <gtk>gtk+-3.0</gtk>
+ </meta>
+
+ <!-- The special macro "${project}" refers to the directory where
+ this bundle file is located. The application name and bundle
+ identifier are taken from the plist file.
+ -->
+ <plist>${project}/inkscape.plist</plist>
+
+ <main-binary>${prefix}/bin/inkscape</main-binary>
+
+ <!-- Copy in the input methods. Dunno if they actually work with
+ OSX. Note the ${gtkdir} macro, which expands to the correct
+ library subdirectory for the specified gtk version. -->
+ <binary>
+ ${prefix}/lib/${gtkdir}/${pkg:${gtk}:gtk_binary_version}/immodules/*.so
+ </binary>
+
+<!-- And the print backends -->
+ <binary>
+ ${prefix}/lib/${gtkdir}/${pkg:${gtk}:gtk_binary_version}/printbackends/*.so
+ </binary>
+
+<!-- Starting with 2.24, gdk-pixbuf installs into its own directory. -->
+ <binary>
+ ${prefix}/lib/gdk-pixbuf-2.0/${pkg:gdk-pixbuf-2.0:gdk_pixbuf_binary_version}/loaders/*.so
+ </binary>
+
+<!-- No longer needed for pango >= 1.38
+ <binary>
+ ${prefix}/lib/pango/${pkg:pango:pango_module_version}/modules/
+ </binary>
+-->
+ <binary>
+ ${prefix}/lib/inkscape/libinkscape_base.dylib
+ </binary>
+ <binary>
+ ${prefix}/lib/libexslt.0.dylib
+ </binary>
+ <binary>
+ ${prefix}/lib/libssl.1.1.dylib
+ </binary>
+ <binary>
+ ${prefix}/lib/gio/modules/*.so
+ </binary>
+ <!-- GObject Introspection repository manager -->
+ <binary>
+ ${prefix}/lib/libgirepository-1.0.1.dylib
+ </binary>
+ <!-- Ghostscript -->
+ <binary>
+ ${prefix}/bin/gs
+ </binary>
+ <!-- Translation filenames, one for each program or library that you
+ want to copy in to the bundle. The "dest" attribute is
+ optional, as usual. Bundler will find all translations of that
+ library/program under the indicated directory and copy them.-->
+ <translations name="gdk-pixbuf">
+ ${prefix}/share/locale
+ </translations>
+ <translations name="gdl-3">
+ ${prefix}/share/locale
+ </translations>
+ <translations name="gtk30">
+ ${prefix}/share/locale
+ </translations>
+ <translations name="gtk30-properties">
+ ${prefix}/share/locale
+ </translations>
+ <translations name="inkscape">
+ ${prefix}/share/locale
+ </translations>
+
+ <!-- Data to copy in, usually Glade/UI files, images, sounds files
+ etc. The destination inside the bundle can be specified if the
+ files should end up at a different location, by using the
+ "dest" property. The destination must then start with the macro
+ "${bundle}", which refers to the bundle root directory.
+ -->
+ <!-- data>
+ ${prefix}/share/gtk3-demo
+ </data -->
+
+ <data>
+ ${prefix}/etc/fonts
+ </data>
+ <data>
+ ${prefix}/share/fontconfig
+ </data>
+ <data>
+ ${prefix}/share/glib-2.0
+ </data>
+ <data>
+ ${prefix}/share/icons
+ </data>
+ <data>
+ ${prefix}/share/ImageMagick-6
+ </data>
+ <data>
+ ${prefix}/share/inkscape
+ </data>
+ <data>
+ ${prefix}/share/ghostscript
+ </data>
+ <data>
+ ${prefix}/bin/ps2pdf*
+ </data>
+ <!-- Copy in the themes data. You may want to trim this to save space
+ in your bundle. -->
+ <data>
+ ${prefix}/share/themes
+ </data>
+
+ <!-- Copy icons. Note that the .icns file is an Apple format which
+ contains up to 4 sizes of icon. You can use
+ /Developer/Applications/Utilities/Icon Composer.app to import
+ artwork and create the file. >
+ <data dest="${bundle}/Contents/Resources">
+ ${project}/Giggle.icns
+ </data -->
+
+ </app-bundle>
diff --git a/packaging/macos/inkscape.plist b/packaging/macos/inkscape.plist
new file mode 100644
index 0000000..1931285
--- /dev/null
+++ b/packaging/macos/inkscape.plist
@@ -0,0 +1,565 @@
+<?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>CFBundleDocumentTypes</key>
+ <array>
+ <dict>
+ <key>CFBundleTypeExtensions</key>
+ <array>
+ <string>svg</string>
+ <string>SVG</string>
+ </array>
+ <key>CFBundleTypeIconFile</key>
+ <string>image-svg+xml.icns</string>
+ <key>CFBundleTypeMIMETypes</key>
+ <array>
+ <string>image/svg+xml</string>
+ </array>
+ <key>CFBundleTypeName</key>
+ <string>Scalable Vector Graphics Image</string>
+ <key>CFBundleTypeOSTypes</key>
+ <array>
+ <string>SVG </string>
+ </array>
+ <key>CFBundleTypeRole</key>
+ <string>Editor</string>
+ <key>LSHandlerRank</key>
+ <string>Owner</string>
+ </dict>
+ <dict>
+ <key>CFBundleTypeExtensions</key>
+ <array>
+ <string>svgz</string>
+ <string>SVGZ</string>
+ </array>
+ <key>CFBundleTypeIconFile</key>
+ <string>image-svg+xml-compressed.icns</string>
+ <key>CFBundleTypeMIMETypes</key>
+ <array>
+ <string>image/svg+xml-compressed</string>
+ </array>
+ <key>CFBundleTypeName</key>
+ <string>Compressed Scalable Vector Graphics Image</string>
+ <key>CFBundleTypeOSTypes</key>
+ <array>
+ <string>SVGZ</string>
+ </array>
+ <key>CFBundleTypeRole</key>
+ <string>Editor</string>
+ <key>LSHandlerRank</key>
+ <string>Owner</string>
+ </dict>
+ <dict>
+ <key>CFBundleTypeExtensions</key>
+ <array>
+ <string>eps</string>
+ <string>epsi</string>
+ <string>EPS</string>
+ <string>EPSI</string>
+ </array>
+ <key>CFBundleTypeIconFile</key>
+ <string>image-x-eps.icns</string>
+ <key>CFBundleTypeMIMETypes</key>
+ <array>
+ <string>application/postscript</string>
+ <string>image/x-eps</string>
+ </array>
+ <key>CFBundleTypeName</key>
+ <string>PostScript Document</string>
+ <key>CFBundleTypeOSTypes</key>
+ <array>
+ <string>EPS </string>
+ <string>EPSI</string>
+ </array>
+ <key>CFBundleTypeRole</key>
+ <string>Editor</string>
+ <key>LSHandlerRank</key>
+ <string>Alternate</string>
+ <key>LSItemContentTypes</key>
+ <array>
+ <string>com.adobe.encapsulated-postscript</string>
+ </array>
+ </dict>
+ <dict>
+ <key>CFBundleTypeExtensions</key>
+ <array>
+ <string>ps</string>
+ <string>PS</string>
+ </array>
+ <key>CFBundleTypeIconFile</key>
+ <string>image-x-ps.icns</string>
+ <key>CFBundleTypeMIMETypes</key>
+ <array>
+ <string>application/postscript</string>
+ <string>image/x-ps</string>
+ </array>
+ <key>CFBundleTypeName</key>
+ <string>PostScript Document</string>
+ <key>CFBundleTypeOSTypes</key>
+ <array>
+ <string>PS </string>
+ </array>
+ <key>CFBundleTypeRole</key>
+ <string>Editor</string>
+ <key>LSHandlerRank</key>
+ <string>Alternate</string>
+ <key>LSItemContentTypes</key>
+ <array>
+ <string>com.adobe.postscript</string>
+ </array>
+ </dict>
+ <dict>
+ <key>CFBundleTypeExtensions</key>
+ <array>
+ <string>ai</string>
+ <string>AI</string>
+ </array>
+ <key>CFBundleTypeIconFile</key>
+ <string>application-illustrator.icns</string>
+ <key>CFBundleTypeMIMETypes</key>
+ <array>
+ <string>application/illustrator</string>
+ <string>application/pdf</string>
+ </array>
+ <key>CFBundleTypeName</key>
+ <string>Adobe Illustrator Document</string>
+ <key>CFBundleTypeOSTypes</key>
+ <array>
+ <string>AI </string>
+ </array>
+ <key>CFBundleTypeRole</key>
+ <string>Viewer</string>
+ <key>LSHandlerRank</key>
+ <string>Alternate</string>
+ <key>LSItemContentTypes</key>
+ <array>
+ <string>com.adobe.illustrator.ai-image</string>
+ </array>
+ </dict>
+ <dict>
+ <key>CFBundleTypeExtensions</key>
+ <array>
+ <string>aisvg</string>
+ <string>AISVG</string>
+ </array>
+ <key>CFBundleTypeIconFile</key>
+ <string>application-illustrator-svg.icns</string>
+ <key>CFBundleTypeMIMETypes</key>
+ <array>
+ <string>application/illustrator-svg</string>
+ </array>
+ <key>CFBundleTypeName</key>
+ <string>Adobe Illustrator SVG Document</string>
+ <key>CFBundleTypeOSTypes</key>
+ <array>
+ <string>AISVG</string>
+ </array>
+ <key>CFBundleTypeRole</key>
+ <string>Viewer</string>
+ </dict>
+ <dict>
+ <key>CFBundleTypeExtensions</key>
+ <array>
+ <string>wmf</string>
+ <string>WMF</string>
+ </array>
+ <key>CFBundleTypeIconFile</key>
+ <string>image-vnd.windows-metafile.icns</string>
+ <key>CFBundleTypeMIMETypes</key>
+ <array>
+ <string>image/vnd.windows-metafile</string>
+ </array>
+ <key>CFBundleTypeName</key>
+ <string>Windows MetaFile</string>
+ <key>CFBundleTypeOSTypes</key>
+ <array>
+ <string>WMF </string>
+ </array>
+ <key>CFBundleTypeRole</key>
+ <string>Editor</string>
+ <key>LSHandlerRank</key>
+ <string>Alternate</string>
+ </dict>
+ <dict>
+ <key>CFBundleTypeExtensions</key>
+ <array>
+ <string>pdf</string>
+ <string>PDF</string>
+ </array>
+ <key>CFBundleTypeIconFile</key>
+ <string>application-pdf.icns</string>
+ <key>CFBundleTypeMIMETypes</key>
+ <array>
+ <string>application/pdf</string>
+ </array>
+ <key>CFBundleTypeName</key>
+ <string>NSPDFPboardType</string>
+ <key>CFBundleTypeOSTypes</key>
+ <array>
+ <string>PDF </string>
+ </array>
+ <key>CFBundleTypeRole</key>
+ <string>Editor</string>
+ <key>LSHandlerRank</key>
+ <string>Alternate</string>
+ <key>LSItemContentTypes</key>
+ <array>
+ <string>com.adobe.pdf</string>
+ </array>
+ </dict>
+ <dict>
+ <key>CFBundleTypeExtensions</key>
+ <array>
+ <string>ggr</string>
+ <string>GGR</string>
+ </array>
+ <key>CFBundleTypeIconFile</key>
+ <string>application-gimp-gradient.icns</string>
+ <key>CFBundleTypeMIMETypes</key>
+ <array>
+ <string>application/x-gimp-gradient</string>
+ </array>
+ <key>CFBundleTypeName</key>
+ <string>Gimp Gradient</string>
+ <key>CFBundleTypeRole</key>
+ <string>Viewer</string>
+ <key>LSHandlerRank</key>
+ <string>Alternate</string>
+ <key>LSItemContentTypes</key>
+ <array>
+ <string>org.gimp.ggr</string>
+ </array>
+ </dict>
+ <dict>
+ <key>CFBundleTypeExtensions</key>
+ <array>
+ <string>ccx</string>
+ <string>CCX</string>
+ </array>
+ <key>CFBundleTypeIconFile</key>
+ <string>application-vnd.corel-draw-compressed.icns</string>
+ <key>CFBundleTypeMIMETypes</key>
+ <array>
+ <string>application/vnd.corel-draw-compressed</string>
+ </array>
+ <key>CFBundleTypeName</key>
+ <string>Corel Draw Compressed Document</string>
+ <key>CFBundleTypeOSTypes</key>
+ <array>
+ <string>CCX </string>
+ </array>
+ <key>CFBundleTypeRole</key>
+ <string>Viewer</string>
+ <key>LSHandlerRank</key>
+ <string>Alternate</string>
+ </dict>
+ <dict>
+ <key>CFBundleTypeExtensions</key>
+ <array>
+ <string>cdt</string>
+ <string>CDT</string>
+ </array>
+ <key>CFBundleTypeIconFile</key>
+ <string>application-vnd.corel-draw-template.icns</string>
+ <key>CFBundleTypeMIMETypes</key>
+ <array>
+ <string>application/vnd.corel-draw-template</string>
+ </array>
+ <key>CFBundleTypeName</key>
+ <string>Corel Draw Template</string>
+ <key>CFBundleTypeOSTypes</key>
+ <array>
+ <string>CDT </string>
+ </array>
+ <key>CFBundleTypeRole</key>
+ <string>Viewer</string>
+ <key>LSHandlerRank</key>
+ <string>Alternate</string>
+ </dict>
+ <dict>
+ <key>CFBundleTypeExtensions</key>
+ <array>
+ <string>cdr</string>
+ <string>CDR</string>
+ </array>
+ <key>CFBundleTypeIconFile</key>
+ <string>application-vnd.corel-draw.icns</string>
+ <key>CFBundleTypeMIMETypes</key>
+ <array>
+ <string>application/vnd.corel-draw</string>
+ </array>
+ <key>CFBundleTypeName</key>
+ <string>Corel Draw Compressed Document</string>
+ <key>CFBundleTypeOSTypes</key>
+ <array>
+ <string>CDR </string>
+ </array>
+ <key>CFBundleTypeRole</key>
+ <string>Viewer</string>
+ <key>LSHandlerRank</key>
+ <string>Alternate</string>
+ </dict>
+ <dict>
+ <key>CFBundleTypeExtensions</key>
+ <array>
+ <string>xaml</string>
+ <string>XAML</string>
+ </array>
+ <key>CFBundleTypeIconFile</key>
+ <string>application-vnd.ms.xaml.icns</string>
+ <key>CFBundleTypeMIMETypes</key>
+ <array>
+ <string>application/vnd.ms.xaml</string>
+ </array>
+ <key>CFBundleTypeName</key>
+ <string>Extensible Application Markup Language (XAML)</string>
+ <key>CFBundleTypeOSTypes</key>
+ <array>
+ <string>XAML</string>
+ </array>
+ <key>CFBundleTypeRole</key>
+ <string>Editor</string>
+ <key>LSHandlerRank</key>
+ <string>Alternate</string>
+ </dict>
+ <dict>
+ <key>CFBundleTypeExtensions</key>
+ <array>
+ <string>dxf</string>
+ <string>DXF</string>
+ </array>
+ <key>CFBundleTypeIconFile</key>
+ <string>image-vnd.dxf.icns</string>
+ <key>CFBundleTypeMIMETypes</key>
+ <array>
+ <string>image/vnd.dxf</string>
+ </array>
+ <key>CFBundleTypeName</key>
+ <string>AutoCAD Document</string>
+ <key>CFBundleTypeOSTypes</key>
+ <array>
+ <string>DXF </string>
+ </array>
+ <key>CFBundleTypeRole</key>
+ <string>Editor</string>
+ <key>LSHandlerRank</key>
+ <string>Alternate</string>
+ </dict>
+ <dict>
+ <key>CFBundleTypeExtensions</key>
+ <array>
+ <string>sk1</string>
+ <string>SK1</string>
+ </array>
+ <key>CFBundleTypeIconFile</key>
+ <string>image-vnd.sk1.icns</string>
+ <key>CFBundleTypeMIMETypes</key>
+ <array>
+ <string>image/vnd.sk1</string>
+ </array>
+ <key>CFBundleTypeName</key>
+ <string>Sketch Document</string>
+ <key>CFBundleTypeOSTypes</key>
+ <array>
+ <string>SK1 </string>
+ </array>
+ <key>CFBundleTypeRole</key>
+ <string>Viewer</string>
+ <key>LSHandlerRank</key>
+ <string>Alternate</string>
+ </dict>
+ <dict>
+ <key>CFBundleTypeExtensions</key>
+ <array>
+ <string>wpg</string>
+ <string>WPG</string>
+ </array>
+ <key>CFBundleTypeIconFile</key>
+ <string>application-vnd.wordperfect-graphic.icns</string>
+ <key>CFBundleTypeMIMETypes</key>
+ <array>
+ <string>application/vnd.wordperfect-graphic</string>
+ <string>image/x-wpg</string>
+ </array>
+ <key>CFBundleTypeName</key>
+ <string>WordPerfect Graphics</string>
+ <key>CFBundleTypeOSTypes</key>
+ <array>
+ <string>WPG </string>
+ </array>
+ <key>CFBundleTypeRole</key>
+ <string>Viewer</string>
+ <key>LSHandlerRank</key>
+ <string>Alternate</string>
+ </dict>
+ <dict>
+ <key>CFBundleTypeExtensions</key>
+ <array>
+ <string>emf</string>
+ <string>EMF</string>
+ </array>
+ <key>CFBundleTypeIconFile</key>
+ <string>Inkscape-Generic.icns</string>
+ <key>CFBundleTypeMIMETypes</key>
+ <array>
+ <string>image/emf</string>
+ </array>
+ <key>CFBundleTypeName</key>
+ <string>Windows Enhanced Metafile document</string>
+ <key>CFBundleTypeOSTypes</key>
+ <array>
+ <string>EMF </string>
+ </array>
+ <key>CFBundleTypeRole</key>
+ <string>Editor</string>
+ <key>LSHandlerRank</key>
+ <string>Alternate</string>
+ </dict>
+ <dict>
+ <key>CFBundleTypeExtensions</key>
+ <array>
+ <string>vsd</string>
+ <string>VSD</string>
+ </array>
+ <key>CFBundleTypeIconFile</key>
+ <string>Inkscape-Generic.icns</string>
+ <key>CFBundleTypeMIMETypes</key>
+ <array>
+ <string>application/vnd.visio</string>
+ </array>
+ <key>CFBundleTypeName</key>
+ <string>Microsoft Visio Diagram</string>
+ <key>CFBundleTypeRole</key>
+ <string>Viewer</string>
+ <key>LSHandlerRank</key>
+ <string>Alternate</string>
+ </dict>
+ <dict>
+ <key>CFBundleTypeExtensions</key>
+ <array>
+ <string>vdx</string>
+ <string>VDX</string>
+ </array>
+ <key>CFBundleTypeIconFile</key>
+ <string>Inkscape-Generic.icns</string>
+ <key>CFBundleTypeMIMETypes</key>
+ <array>
+ <string>application/vnd.visio</string>
+ </array>
+ <key>CFBundleTypeName</key>
+ <string>Microsoft Visio XML Diagram</string>
+ <key>CFBundleTypeRole</key>
+ <string>Viewer</string>
+ <key>LSHandlerRank</key>
+ <string>Alternate</string>
+ </dict>
+ <dict>
+ <key>CFBundleTypeExtensions</key>
+ <array>
+ <string>vsdm</string>
+ <string>VSDM</string>
+ <string>vsdx</string>
+ <string>VSDX</string>
+ </array>
+ <key>CFBundleTypeIconFile</key>
+ <string>Inkscape-Generic.icns</string>
+ <key>CFBundleTypeMIMETypes</key>
+ <array>
+ <string>application/vnd.visio</string>
+ </array>
+ <key>CFBundleTypeName</key>
+ <string>Microsoft Visio 2013 drawing</string>
+ <key>CFBundleTypeRole</key>
+ <string>Viewer</string>
+ <key>LSHandlerRank</key>
+ <string>Alternate</string>
+ </dict>
+ <dict>
+ <key>CFBundleTypeExtensions</key>
+ <array>
+ <string>plt</string>
+ <string>PLT</string>
+ <string>hpgl</string>
+ <string>hpgl</string>
+ </array>
+ <key>CFBundleTypeIconFile</key>
+ <string>Inkscape-Generic.icons</string>
+ <key>CFBundleTypeMIMETypes</key>
+ <array>
+ <string>application/vnd.hp-HPGL</string>
+ <string>image/hpgl</string>
+ <string>image/plt</string>
+ <string>image/x-plt</string>
+ </array>
+ <key>CFBundleTypeName</key>
+ <string>HP Graphics Language file</string>
+ <key>CFBundleTypeRole</key>
+ <string>Editor</string>
+ <key>LSHandlerRank</key>
+ <string>Alternate</string>
+ </dict>
+ <dict>
+ <key>CFBundleTypeExtensions</key>
+ <array>
+ <string>png</string>
+ <string>PNG</string>
+ </array>
+ <key>CFBundleTypeIconFile</key>
+ <string>Inkscape-Generic.icns</string>
+ <key>CFBundleTypeMIMETypes</key>
+ <array>
+ <string>image/png</string>
+ </array>
+ <key>CFBundleTypeName</key>
+ <string>Portable Network Graphics Image</string>
+ <key>CFBundleTypeOSTypes</key>
+ <array>
+ <string>PNGf</string>
+ </array>
+ <key>CFBundleTypeRole</key>
+ <string>Viewer</string>
+ <key>LSHandlerRank</key>
+ <string>None</string>
+ </dict>
+ </array>
+ <key>CFBundleExecutable</key>
+ <string>inkscape</string>
+ <key>CFBundleName</key>
+ <string>Inkscape</string>
+ <key>CFBundleIconFile</key>
+ <string>inkscape.icns</string>
+ <key>CFBundleIdentifier</key>
+ <string>org.inkscape.Inkscape</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+ <key>CFBundleShortVersionString</key>
+ <string>CFBundleShortVersionString</string>
+ <key>CFBundleSignature</key>
+ <string>Inks</string>
+ <key>CFBundleVersion</key>
+ <string>CFBundleVersion</string>
+ <key>NSHumanReadableCopyright</key>
+ <string>Copyright © 2020 Inkscape Developers, GNU General Public License.</string>
+ <key>LSApplicationCategoryType</key>
+ <string>public.app-category.graphics-design</string>
+ <key>LSMinimumSystemVersion</key>
+ <string>LSMinimumSystemVersion</string>
+ <key>NSDesktopFolderUsageDescription</key>
+ <string>Inkscape needs your permission to access the Desktop folder.</string>
+ <key>NSDocumentsFolderUsageDescription</key>
+ <string>Inkscape needs your permission to access the Documents folder.</string>
+ <key>NSDownloadsFolderUsageDescription</key>
+ <string>Inkscape needs your permission to access the Downloads folder.</string>
+ <key>NSRemoveableVolumesUsageDescription</key>
+ <string>Inkscape needs your permission to access removable volumes.</string>
+ <key>NSHighResolutionCapable</key>
+ <true/>
+ <key>NSRequiresAquaSystemAppearance</key>
+ <false/>
+ </dict>
+</plist>
diff --git a/packaging/macos/inkscape_dmg.py b/packaging/macos/inkscape_dmg.py
new file mode 100644
index 0000000..266e27b
--- /dev/null
+++ b/packaging/macos/inkscape_dmg.py
@@ -0,0 +1,246 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+import biplist
+import os.path
+
+#
+# Example settings file for dmgbuild
+#
+
+# Use like this: dmgbuild -s settings.py "Test Volume" test.dmg
+
+# You can actually use this file for your own application (not just TextEdit)
+# by doing e.g.
+#
+# dmgbuild -s settings.py -D app=/path/to/My.app "My Application" MyApp.dmg
+
+# .. Useful stuff ..............................................................
+
+application = defines.get('app', 'PLACEHOLDERAPPLICATION')
+appname = os.path.basename(application)
+
+def icon_from_app(app_path):
+ plist_path = os.path.join(app_path, 'Contents', 'Info.plist')
+ plist = biplist.readPlist(plist_path)
+ icon_name = plist['CFBundleIconFile']
+ icon_root,icon_ext = os.path.splitext(icon_name)
+ if not icon_ext:
+ icon_ext = '.icns'
+ icon_name = icon_root + icon_ext
+ return os.path.join(app_path, 'Contents', 'Resources', icon_name)
+
+# .. Basics ....................................................................
+
+# Uncomment to override the output filename
+# filename = 'test.dmg'
+
+# Uncomment to override the output volume name
+# volume_name = 'Test'
+
+# Volume format (see hdiutil create -help)
+format = defines.get('format', 'UDBZ')
+
+# Volume size
+size = defines.get('size', None)
+
+# Files to include
+files = [ application ]
+
+# Symlinks to create
+#symlinks = { 'Applications': '/Applications' }
+
+# Volume icon
+#
+# You can either define icon, in which case that icon file will be copied to the
+# image, *or* you can define badge_icon, in which case the icon file you specify
+# will be used to badge the system's Removable Disk icon
+#
+icon = 'PLACEHOLDERICON'
+#badge_icon = icon_from_app(application)
+
+# Where to put the icons
+icon_locations = {
+ appname: (390, 240),
+# 'Applications': (500, 120)
+ }
+
+# .. Window configuration ......................................................
+
+# Background
+#
+# This is a STRING containing any of the following:
+#
+# #3344ff - web-style RGB color
+# #34f - web-style RGB color, short form (#34f == #3344ff)
+# rgb(1,0,0) - RGB color, each value is between 0 and 1
+# hsl(120,1,.5) - HSL (hue saturation lightness) color
+# hwb(300,0,0) - HWB (hue whiteness blackness) color
+# cmyk(0,1,0,0) - CMYK color
+# goldenrod - X11/SVG named color
+# builtin-arrow - A simple built-in background with a blue arrow
+# /foo/bar/baz.png - The path to an image file
+#
+# The hue component in hsl() and hwb() may include a unit; it defaults to
+# degrees ('deg'), but also supports radians ('rad') and gradians ('grad'
+# or 'gon').
+#
+# Other color components may be expressed either in the range 0 to 1, or
+# as percentages (e.g. 60% is equivalent to 0.6).
+background = 'PLACEHOLDERBACKGROUND'
+
+show_status_bar = False
+show_tab_view = False
+show_toolbar = False
+show_pathbar = False
+show_sidebar = False
+sidebar_width = 180
+
+# Window position in ((x, y), (w, h)) format
+window_rect = ((100, 100), (500, 380))
+
+# Select the default view; must be one of
+#
+# 'icon-view'
+# 'list-view'
+# 'column-view'
+# 'coverflow'
+#
+default_view = 'icon-view'
+
+# General view configuration
+show_icon_preview = False
+
+# Set these to True to force inclusion of icon/list view settings (otherwise
+# we only include settings for the default view)
+include_icon_view_settings = 'auto'
+include_list_view_settings = 'auto'
+
+# .. Icon view configuration ...................................................
+
+arrange_by = None
+grid_offset = (0, 0)
+grid_spacing = 100
+scroll_position = (0, 0)
+label_pos = 'bottom' # or 'right'
+text_size = 12
+icon_size = 64
+
+# .. List view configuration ...................................................
+
+# Column names are as follows:
+#
+# name
+# date-modified
+# date-created
+# date-added
+# date-last-opened
+# size
+# kind
+# label
+# version
+# comments
+#
+list_icon_size = 16
+list_text_size = 12
+list_scroll_position = (0, 0)
+list_sort_by = 'name'
+list_use_relative_dates = True
+list_calculate_all_sizes = False,
+list_columns = ('name', 'date-modified', 'size', 'kind', 'date-added')
+list_column_widths = {
+ 'name': 300,
+ 'date-modified': 181,
+ 'date-created': 181,
+ 'date-added': 181,
+ 'date-last-opened': 181,
+ 'size': 97,
+ 'kind': 115,
+ 'label': 100,
+ 'version': 75,
+ 'comments': 300,
+ }
+list_column_sort_directions = {
+ 'name': 'ascending',
+ 'date-modified': 'descending',
+ 'date-created': 'descending',
+ 'date-added': 'descending',
+ 'date-last-opened': 'descending',
+ 'size': 'descending',
+ 'kind': 'ascending',
+ 'label': 'ascending',
+ 'version': 'ascending',
+ 'comments': 'ascending',
+ }
+
+# .. License configuration .....................................................
+
+# Text in the license configuration is stored in the resources, which means
+# it gets stored in a legacy Mac encoding according to the language. dmgbuild
+# will *try* to convert Unicode strings to the appropriate encoding, *but*
+# you should be aware that Python doesn't support all of the necessary encodings;
+# in many cases you will need to encode the text yourself and use byte strings
+# instead here.
+
+# Recognized language names are:
+#
+# af_ZA, ar, be_BY, bg_BG, bn, bo, br, ca_ES, cs_CZ, cy, da_DK, de_AT, de_CH,
+# de_DE, dz_BT, el_CY, el_GR, en_AU, en_CA, en_GB, en_IE, en_SG, en_US, eo,
+# es_419, es_ES, et_EE, fa_IR, fi_FI, fo_FO, fr_001, fr_BE, fr_CA, fr_CH,
+# fr_FR, ga-Latg_IE, ga_IE, gd, grc, gu_IN, gv, he_IL, hi_IN, hr_HR, hu_HU,
+# hy_AM, is_IS, it_CH, it_IT, iu_CA, ja_JP, ka_GE, kl, ko_KR, lt_LT, lv_LV,
+# mk_MK, mr_IN, mt_MT, nb_NO, ne_NP, nl_BE, nl_NL, nn_NO, pa, pl_PL, pt_BR,
+# pt_PT, ro_RO, ru_RU, se, sk_SK, sl_SI, sr_RS, sv_SE, th_TH, to_TO, tr_TR,
+# uk_UA, ur_IN, ur_PK, uz_UZ, vi_VN, zh_CN, zh_TW
+
+# license = {
+# 'default-language': 'en_US',
+# 'licenses': {
+# # For each language, the text of the license. This can be plain text,
+# # RTF (in which case it must start "{\rtf1"), or a path to a file
+# # containing the license text. If you're using RTF,
+# # watch out for Python escaping (or read it from a file).
+# 'English': b'''{\\rtf1\\ansi\\ansicpg1252\\cocoartf1504\\cocoasubrtf820
+# {\\fonttbl\\f0\\fnil\\fcharset0 Helvetica-Bold;\\f1\\fnil\\fcharset0 Helvetica;}
+# {\\colortbl;\\red255\\green255\\blue255;\\red0\\green0\\blue0;}
+# {\\*\\expandedcolortbl;;\\cssrgb\\c0\\c0\\c0;}
+# \\paperw11905\\paperh16837\\margl1133\\margr1133\\margb1133\\margt1133
+# \\deftab720
+# \\pard\\pardeftab720\\sa160\\partightenfactor0
+
+# \\f0\\b\\fs60 \\cf2 \\expnd0\\expndtw0\\kerning0
+# \\up0 \\nosupersub \\ulnone \\outl0\\strokewidth0 \\strokec2 Test License\\
+# \\pard\\pardeftab720\\sa160\\partightenfactor0
+
+# \\fs36 \\cf2 \\strokec2 What is this?\\
+# \\pard\\pardeftab720\\sa160\\partightenfactor0
+
+# \\f1\\b0\\fs22 \\cf2 \\strokec2 This is the English license. It says what you are allowed to do with this software.\\
+# \\
+# }''',
+# },
+# 'buttons': {
+# # For each language, text for the buttons on the licensing window.
+# #
+# # Default buttons and text are built-in for the following languages:
+# #
+# # English (en_US), German (de_DE), Spanish (es_ES), French (fr_FR),
+# # Italian (it_IT), Japanese (ja_JP), Dutch (nl_NL), Swedish (sv_SE),
+# # Brazilian Portuguese (pt_BR), Simplified Chinese (zh_CN),
+# # Traditional Chinese (zh_TW), Danish (da_DK), Finnish (fi_FI),
+# # Korean (ko_KR), Norwegian (nb_NO)
+# #
+# # You don't need to specify them for those languages; if you fail to
+# # specify them for some other language, English will be used instead.
+
+# 'en_US': (
+# b'English',
+# b'Agree',
+# b'Disagree',
+# b'Print',
+# b'Save',
+# b'If you agree with the terms of this license, press "Agree" to '
+# b'install the software. If you do not agree, press "Disagree".'
+# ),
+# },
+# }
diff --git a/packaging/macos/install_toolset.sh b/packaging/macos/install_toolset.sh
new file mode 100755
index 0000000..5b4259a
--- /dev/null
+++ b/packaging/macos/install_toolset.sh
@@ -0,0 +1,66 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# This file is part of the build pipeline for Inkscape on macOS.
+#
+# ### install_toolset.sh ###
+# Install a pre-compiled version of the JHBuild toolset and required
+# dependencies for Inkscape.
+
+### load settings and functions ################################################
+
+SELF_DIR=$(F=$0; while [ ! -z $(readlink $F) ] && F=$(readlink $F); \
+ cd $(dirname $F); F=$(basename $F); [ -L $F ]; do :; done; echo $(pwd -P))
+for script in $SELF_DIR/0??-*.sh; do source $script; done
+
+set -e
+
+### install toolset ############################################################
+
+function install
+{
+ local toolset_dmg=$TOOLSET_REPO_DIR/$(basename $URL_TOOLSET)
+
+ if [ -f $toolset_dmg ]; then
+ echo_info "no download required"
+ else
+ # File not present on disk, we need to download.
+ echo_act "download required"
+ save_file $URL_TOOLSET $TOOLSET_REPO_DIR
+ echo_ok "download successful"
+ fi
+
+ # mount build system read-only
+ local device=$(create_dmg_device $toolset_dmg)
+ [ ! -d $WRK_DIR ] && mkdir -p $WRK_DIR
+ mount -o nobrowse,noowners,ro -t hfs $device $WRK_DIR
+ echo_ok "toolset mounted as $device"
+
+ # Sadly, there are some limitations involved with union-mounting:
+ # - Files are not visible to 'ls'.
+ # - You cannot write in a location without having written to its
+ # parent location. That's why we need to pre-create all directories
+ # below.
+ #
+ # Shadow-mounting a dmg is not a feasible alternative due to its
+ # bad write-performance.
+
+ # prepare a script for mass-creating directories
+ find $OPT_DIR -type d ! -path "$TMP_DIR/*" ! -path "$SRC_DIR/*" \
+ -exec echo "mkdir {}" > $TOOLSET_ROOT_DIR/create_dirs.sh \;
+ chmod 755 $TOOLSET_ROOT_DIR/create_dirs.sh
+
+ # create writable (ramdisk-) overlay
+ device=$(create_ram_device $OVERLAY_RAMDISK_SIZE build)
+ mount -o nobrowse,rw,union -t hfs $device $WRK_DIR
+ echo_ok "writable ramdisk overlay mounted as $device"
+
+ # create all directories inside overlay
+ $TOOLSET_ROOT_DIR/create_dirs.sh
+ rm $TOOLSET_ROOT_DIR/create_dirs.sh
+}
+
+### main #######################################################################
+
+$SELF_DIR/110-sysprep.sh
+install
diff --git a/packaging/macos/uninstall_toolset.sh b/packaging/macos/uninstall_toolset.sh
new file mode 100755
index 0000000..ba92577
--- /dev/null
+++ b/packaging/macos/uninstall_toolset.sh
@@ -0,0 +1,36 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# This file is part of the build pipeline for Inkscape on macOS.
+#
+# ### uninstall_toolset.sh ###
+# Uninstall a previously installed toolset. In this case, "uninstall" means
+# "unmount", the downloaded .dmg won't be deleted.
+
+### load settings and functions ################################################
+
+SELF_DIR=$(F=$0; while [ ! -z $(readlink $F) ] && F=$(readlink $F); \
+ cd $(dirname $F); F=$(basename $F); [ -L $F ]; do :; done; echo $(pwd -P))
+for script in $SELF_DIR/0??-*.sh; do source $script; done
+
+set -e
+
+### uninstall toolset ##########################################################
+
+function uninstall
+{
+ while : ; do # unmount everything (in reverse order)
+ local disk=$(mount | grep $WRK_DIR | tail -n1 | awk '{ print $1 }')
+
+ if [ ${#disk} -eq 0 ]; then
+ break # nothing to do here
+ else
+ diskutil eject $disk > /dev/null # unmount
+ echo_ok "ejected $disk"
+ fi
+ done
+}
+
+### main #######################################################################
+
+uninstall