build, gitlab-ci, meson: Add macOS builds to our CI

This adds macOS support on our CI aiming to MacPorts + macOS SDK 11. But since
the GNOME runner have short timeout and no cache we fallback to Homebrew for
now (which inelegantly links to X11 and follows runner's very high macOS 14).

There is also still work to do: .dmg creation, multi-arch and notarization.

Even so, this is a huge improvement, because macOS devs can now easily test MRs
inhouse and non-macOS devs can contribute for macOS inhouse too. With the word
"inhouse" I mean from this official gimp repo, without needing gimp-macos-build.

For making that possible, I ported various concepts from gimp-macos-build:
- .typelib regeneration for relocatability
- LC_RPATH, LC_LOAD_DYLIB and LC_ID_DYLIB patching for relocatability
- LC_BUILD_VERSION (minos) check for compatibility
- GdkPixBuf loaders.cache patching
- NEW: immodules.cache patching
- Python.framework complex handling
- Ad-hoc code signing on binaries (do not confuse with notarization)
- .dSYM generation for babl, gegl and gimp
- etc
All these concepts into few scripts, without depending on gtk-mac-bundler.

Since it was designed to work with MacPorts, we could make use of it on
gimp-macos-build, but things are so endlessly complicated there into the
9999 scripts and CircleCI is very slow, so I'm not sure if gimp-macos-build
have any future. The ideal would be official macOS 11 builds on GIMP repo.
This commit is contained in:
Bruno Lopes 2025-08-21 15:18:02 -03:00
parent 85b1f052c2
commit 7accd52ce1
12 changed files with 978 additions and 4 deletions

View file

@ -8,6 +8,7 @@ spec:
- GIMP_CI_SNAP #trigger the snap build (base but slow)
- GIMP_CI_WIN_INSTALLER #trigger all native MSYS2 builds then creates Inno Windows installer (base but slow)
- GIMP_CI_MS_STORE #trigger arm64 and x64 native MSYS2 builds then creates a .msixbundle (base but slow)
- GIMP_CI_MACOS #trigger arm64 native macOS builds (base but slow)
- none
default: 'none'
test_pipeline:
@ -78,7 +79,6 @@ workflow:
retry:
max: 1
when:
- 'runner_system_failure'
- 'scheduler_failure'
needs: []
# Default Docker image (keep variables: DEB_VERSION: consistent with devel-docs/os-support.txt)
@ -647,6 +647,62 @@ gimp-win-eol:
artifacts: !reference [gimp-win, artifacts]
## macOS pipelines (Homebrew) ##
.macos-inhouse:
extends: .default
rules:
- if: '$CI_MERGE_REQUEST_LABELS =~ /.*Package:Macos Dmg.*/'
interruptible: true
- if: '$GIMP_CI_MACOS != null || "$[[ inputs.distribution_pipeline ]]" =~ /.*GIMP_CI_MACOS.*/'
tags:
- macos
variables:
#FIXME: Our runner #926 (by MacStadium) do not have cache nor timeout for MacPorts builds
OPT_PREFIX: /opt/homebrew
PKGCONF_RELOCATABLE_OPTION: '-Dpkgconfig.relocatable=true'
DMG_OPTION: '-Ddmg=true'
before_script:
- export GIMP_PREFIX="$PWD/_install-$(uname -m)"
- export PATH="$OPT_PREFIX/bin:$PATH"
timeout: 30m
.macos_environ: &MAC_ENVIRON
- printf "\e[0Ksection_start:`date +%s`:macos_environ[collapsed=true]\r\e[0KPreparing build environment\n"
## Build-time vars
- export PKG_CONFIG_PATH="${GIMP_PREFIX}/lib/pkgconfig:${OPT_PREFIX}/lib/pkgconfig:${OPT_PREFIX}/opt/libarchive/lib/pkgconfig"
- export XDG_DATA_DIRS="${GIMP_PREFIX}/share:${OPT_PREFIX}/share"
## Runtime vars
- export PATH="${GIMP_PREFIX}/bin:${OPT_PREFIX}/bin:$PATH"
- export GI_TYPELIB_PATH="${GIMP_PREFIX}/lib/girepository-1.0:${OPT_PREFIX}/lib/girepository-1.0"
- printf "\e[0Ksection_end:`date +%s`:macos_environ\r\e[0K\n"
deps-macos-inhouse:
extends: .macos-inhouse
stage: dependencies
script:
- sh build/macos/1_build-deps-macports.sh
artifacts:
paths:
- _install-*
- babl/_build-*/meson-logs/meson-log.txt
- gegl/_build-*/meson-logs/meson-log.txt
expire_in: 2 hours
gimp-macos-inhouse:
extends: .macos-inhouse
needs: ["deps-macos-inhouse"]
stage: build
variables:
GIT_SUBMODULE_STRATEGY: recursive
script:
- sh build/macos/2_build-gimp-macports.sh
artifacts:
paths:
- gimp-*.app
- _build*/meson-logs/meson-log.txt
- _build*/done-dylib.list
## Analysis ##
file-plug-in-tests:

View file

@ -0,0 +1,87 @@
#!/bin/sh
# Ensure the script work properly
case $(readlink /proc/$$/exe) in
*bash)
set -o posix
;;
esac
set -e
if [ "$0" != 'build/macos/1_build-deps-macports.sh' ] && [ $(basename "$PWD") != 'macos' ]; then
printf '\033[31m(ERROR)\033[0m: Script called from wrong dir. Please, call this script from the root of gimp git dir/\n'
exit 1
elif [ $(basename "$PWD") = 'macos' ]; then
cd ../../..
fi
if [ -z "$GITLAB_CI" ]; then
GIT_DEPTH='1'
PARENT_DIR='/..'
fi
# Install part of the deps
if [ -z "$OPT_PREFIX" ]; then
export OPT_PREFIX=$(which port | sed 's|/bin/port||' || brew --prefix)
if echo "$OPT_PREFIX" | grep -q 'not found'; then
printf '\033[31m(ERROR)\033[0m: MacPorts installation not found. Please, install it on: https://www.macports.org/install.php\n'
exit 1
fi
fi
if [ "$OPT_PREFIX" != '/opt/local' ] && [ "$OPT_PREFIX" != '/opt/homebrew' ]; then
sed -i .bak "s/^#build_arch.*/build_arch $(uname -m)/" "${OPT_PREFIX}/etc/macports/macports.conf" >/dev/null 2>&1 || true
fi
export MACOSX_DEPLOYMENT_TARGET=$(awk '/LSMinimumSystemVersion/{found=1} found && /<string>/{gsub(/.*<string>|<\/string>.*/, ""); print; exit}' build/macos/Info.plist) #End of config
printf "\e[0Ksection_start:`date +%s`:deps_install[collapsed=true]\r\e[0KInstalling dependencies provided by $( [ -f "$OPT_PREFIX/bin/port" ] && echo MacPorts || echo Homebrew )\n"
if [ -f "$OPT_PREFIX/bin/port" ]; then
eval $( [ "$OPT_PREFIX"=/opt/local ] && echo sudo ) port sync && eval $( [ "$OPT_PREFIX"=/opt/local ] && echo sudo ) port upgrade outdated
eval $( [ "$OPT_PREFIX"=/opt/local ] && echo sudo ) port install -N $(grep -v '^#' build/macos/all-deps-uni.txt | sed 's/|homebrew:[^ ]*//g' | tr -d '\' | xargs)
git apply -v build/macos/patches/0001-meson-Patch-python-version.patch || true
else
brew upgrade --quiet
brew install --quiet $(tr '\\' '\n' < build/macos/all-deps-uni.txt | grep -v '#' | sed -n 's/.*|homebrew://p' | awk '{print $1}' | xargs)
git apply -v build/macos/patches/0001-build-macos-Do-not-require-gexiv2-0.14-on-homebrew.patch || true
fi
git apply -v build/macos/patches/0001-app-libgimpwidgets-meson-plug-ins-Patch-macOS-bundle.patch || true
printf "\e[0Ksection_end:`date +%s`:deps_install\r\e[0K\n"
# Prepare env (only GIMP_PREFIX is needed for flatpak)
GIMP_DIR="$PWD"
cd ${GIMP_DIR}${PARENT_DIR}
if [ -z "$GIMP_PREFIX" ]; then
export GIMP_PREFIX="$PWD/_install"
fi
eval "$(sed -n -e 's/- //' -e '/macos_environ\[/,/macos_environ/p' $GIMP_DIR/.gitlab-ci.yml)"
# Build some deps (including babl and GEGL)
self_build()
{
dep=$(basename "$1" .git)
printf "\e[0Ksection_start:`date +%s`:${dep}_build[collapsed=true]\r\e[0KBuilding $dep\n"
if [ ! -d "$dep" ]; then
git clone --depth $GIT_DEPTH --branch ${2:-master} $1
fi
cd $dep
git pull
# Configure and build
if [ ! -f "_build-$(uname -m)/build.ninja" ]; then
meson setup _build-$(uname -m) -Dprefix="$GIMP_PREFIX" $PKGCONF_RELOCATABLE_OPTION \
-Dbuildtype=debugoptimized \
-Dc_args="-I${OPT_PREFIX}/include" -Dcpp_args="-I${OPT_PREFIX}/include" -Dc_link_args="-L${OPT_PREFIX}/lib" -Dcpp_link_args="-L${OPT_PREFIX}/lib"
fi
cd _build-$(uname -m)
ninja
ninja install
cd ../..
printf "\e[0Ksection_end:`date +%s`:${dep}_build\r\e[0K\n"
}
self_build https://gitlab.gnome.org/GNOME/babl
self_build https://gitlab.gnome.org/GNOME/gegl
cd $GIMP_DIR

View file

@ -0,0 +1,55 @@
#!/bin/sh
# Ensure the script work properly
case $(readlink /proc/$$/exe) in
*bash)
set -o posix
;;
esac
set -e
if [ "$0" != 'build/macos/2_build-gimp-macports.sh' ] && [ $(basename "$PWD") != 'macos' ]; then
printf '\033[31m(ERROR)\033[0m: Script called from wrong dir. Please, call this script from the root of gimp git dir/\n'
exit 1
elif [ $(basename "$PWD") = 'macos' ]; then
cd ../../..
fi
if [ -z "$GITLAB_CI" ]; then
git submodule update --init
NON_RELOCATABLE_OPTION='-Drelocatable-bundle=no'
fi
# Install part of the deps (again)
eval "$(sed -n '/Install part/,/End of config/p' build/macos/1_build-deps-macports.sh)"
if [ "$GITLAB_CI" ]; then
eval "$(sed -n '/deps_install\[/,/deps_install/p' build/macos/1_build-deps-macports.sh)"
fi
# Prepare env (only GIMP_PREFIX is needed for flatpak)
if [ -z "$GIMP_PREFIX" ]; then
export GIMP_PREFIX="$PWD/../_install"
fi
eval "$(sed -n -e 's/- //' -e '/macos_environ\[/,/macos_environ/p' .gitlab-ci.yml)"
# Build GIMP only
printf "\e[0Ksection_start:`date +%s`:gimp_build[collapsed=true]\r\e[0KBuilding GIMP\n"
if [ ! -f "_build-$(uname -m)/build.ninja" ]; then
meson setup _build-$(uname -m) -Dprefix="$GIMP_PREFIX" $NON_RELOCATABLE_OPTION \
$PKGCONF_RELOCATABLE_OPTION $DMG_OPTION \
-Dbuild-id=org.gimp.GIMP_official \
-Dc_args="-I${OPT_PREFIX}/include" -Dcpp_args="-I${OPT_PREFIX}/include" -Dc_link_args="-L${OPT_PREFIX}/lib" -Dcpp_link_args="-L${OPT_PREFIX}/lib"
fi
cd _build-$(uname -m)
ninja
printf "\e[0Ksection_end:`date +%s`:gimp_build\r\e[0K\n"
# Bundle GIMP
printf "\e[0Ksection_start:`date +%s`:gimp_bundle[collapsed=true]\r\e[0K$(if ! grep -q "dmg=true" meson-logs/meson-log.txt; then echo "Installing GIMP as non-relocatable on GIMP_PREFIX"; else echo "Creating .app bundle"; fi)\n"
ninja install > ninja_install.log 2>&1 || { cat ninja_install.log; exit 1; };
cd ..
printf "\e[0Ksection_end:`date +%s`:gimp_bundle\r\e[0K\n"

View file

@ -0,0 +1,344 @@
#!/usr/bin/env python3
import os
import platform
import re
import shutil
import subprocess
import stat
import sys
from pathlib import Path
from glob import glob
# This script is used to create a GIMP .app bundle on macOS. A bundle
# is used as source of files for making the .dmg installer
if not os.getenv("MESON_BUILD_ROOT"):
# Let's prevent contributors from creating broken bundles
print("\033[31m(ERROR)\033[0m: Script called standalone. Please build GIMP targeting DMG installer creation.")
sys.exit(1)
# Get variables from MESON_BUILD_ROOT/config.h that can be used on this script
with open("config.h") as file:
for line in file:
match = re.match(r'^#\s*define\s+(\S+)(?:\s+(.*))?$', line)
if match:
key, value = match.groups()
if value is None or not value.strip():
value = "1" #needed when there is no explicit value
else:
value = value.strip().strip('"').strip("'")
os.environ[key] = value
if not os.getenv("ENABLE_RELOCATABLE_RESOURCES"):
print("\n\033[31m(ERROR)\033[0m: No relocatable GIMP build found. You can build GIMP with '-Drelocatable-bundle=yes' to make a build suitable for .app creation.")
sys.exit(1)
# Bundle deps and GIMP files
GIMP_SOURCE = Path(os.getenv("MESON_SOURCE_ROOT"))
## System prefix: it is OPT_PREFIX (see 1_build-deps-macports)
OPT_PREFIX = Path(os.getenv("OPT_PREFIX"))
## GIMP prefix: as set at meson configure time
GIMP_PREFIX = Path(os.getenv("MESON_INSTALL_DESTDIR_PREFIX"))
## Bundle dir: we make a "perfect" bundle separated from GIMP_PREFIX
#NOTE: The bundling script need to set $OPT_PREFIX to our dist scripts
#fallback code be able to identify what arch they are distributing
GIMP_DISTRIB = Path(GIMP_SOURCE) / f"gimp-{platform.machine()}.app" / "Contents"
def bundle(src_root, pattern, option="None", override=None):
## Search for targets in search path
src_root = Path(src_root)
paths_to_bundle = list(src_root.glob(pattern))
if not paths_to_bundle:
print(f"\033[31m(ERROR)\033[0m: not found {src_root}/{pattern}")
sys.exit(1)
for src_path in paths_to_bundle:
## Copy found targets to bundle path
symlink_cleanup = True
if "--dest" in option:
dest_path = GIMP_DISTRIB / Path(override) / src_path.name
elif "--rename" in option:
dest_path = GIMP_DISTRIB / Path(override)
elif "bin/" in pattern:
symlink_cleanup = False
dest_path = GIMP_DISTRIB / "MacOS" / Path(src_path.relative_to(src_root)).name
elif "lib/" in pattern:
dest_path = GIMP_DISTRIB / "Frameworks" / src_path.relative_to(src_root / "lib")
elif "share/" in pattern:
dest_path = GIMP_DISTRIB / "Resources" / src_path.relative_to(src_root / "share")
elif "etc/" in pattern:
dest_path = GIMP_DISTRIB / "SharedSupport" / src_path.relative_to(src_root / "etc")
dest_path.parent.mkdir(parents=True, exist_ok=True)
print(f"Bundling {src_path} to {dest_path.parent}")
if src_path.is_dir():
try:
shutil.copytree(src_path, dest_path, dirs_exist_ok=True)
except shutil.Error as e:
print(f"\033[33m(WARNING)\033[0m: {dest_path} seems to already exist and have permission problems")
else:
if not str(src_path).endswith(".typelib"):
try:
shutil.copy2(src_path, dest_path, follow_symlinks=symlink_cleanup)
except shutil.Error as e:
print(f"\033[33m(WARNING)\033[0m: {dest_path} seems to already exist and have permission problems")
if "MacOS/" in str(dest_path) and not dest_path.is_symlink():
os.chmod(dest_path, 0o755)
else:
## Process .typelib dependencies (as relocatable)
tmp_gir_dir = GIMP_DISTRIB / "tmp"
tmp_gir_dir.mkdir(parents=True, exist_ok=True)
def set_typelib_rpath(typelib, prefix):
typelib_path = Path(f"{prefix}/lib/girepository-1.0/{typelib}.typelib")
target_path = dest_path.parent / typelib_path.name
if typelib_path.exists() and not target_path.exists():
if typelib_path != src_path:
print(f"Bundling {typelib_path} to {dest_path.parent}")
tmp_gir_path = Path(f"{tmp_gir_dir}/{typelib}.gir")
shutil.copy2(Path(f"{prefix}/share/gir-1.0/{typelib}.gir"), tmp_gir_path)
text = tmp_gir_path.read_text()
text = re.sub(r'shared-library="([^"]+)"', lambda m: 'shared-library="' + ",".join("@rpath/" + os.path.basename(p) for p in m.group(1).split(",")) + '"', text)
tmp_gir_path.write_text(text)
subprocess.run(["g-ir-compiler", f"--includedir={tmp_gir_dir}", str(tmp_gir_path), "-o", target_path], check=True)
def process_typelib(path, typelib_list=None):
set_typelib_rpath(Path(path).stem, GIMP_PREFIX)
if typelib_list is None:
typelib_list = set()
cmd = ['g-ir-inspect', '--print-typelibs', os.path.basename(path).split('-')[0]]
result = subprocess.run(cmd, capture_output=True, text=True)
for line in result.stdout.splitlines():
typelib = line.replace("typelib: ", "").strip()
if typelib and typelib not in typelib_list:
typelib_list.add(typelib)
for prefix in [GIMP_PREFIX, OPT_PREFIX]:
typelib_path = Path(f"{prefix}/lib/girepository-1.0/{typelib}.typelib")
target_path = dest_path.parent / typelib_path.name
if typelib_path.exists() and not target_path.exists():
set_typelib_rpath(typelib, prefix)
process_typelib(typelib, typelib_list)
process_typelib(src_path)
shutil.rmtree(tmp_gir_dir)
def clean(base_path, pattern):
base_path = Path(base_path)
first_found = False
for parent_path in base_path.glob(os.path.dirname(pattern)):
for path in parent_path.rglob(os.path.basename(pattern)):
if path.exists():
if not first_found:
print(f"Cleaning {base_path}/{pattern}")
first_found = True
if path.is_dir():
shutil.rmtree(path)
else:
path.unlink()
## PREPARE BUNDLE
GIMP_DISTRIB.mkdir(parents=True, exist_ok=True)
### Prevent Git going crazy
(GIMP_DISTRIB / ".." / ".gitignore").write_text("*\n")
### Configure Info.plist
text = (GIMP_SOURCE / "build/macos/Info.plist").read_text()
if not os.getenv("GIMP_RELEASE") or os.getenv("GIMP_IS_RC_GIT"):
BUNDLE_IDENTIFIER = "org.gimp.gimp.internal"
BUNDLE_NAME = "GIMP (Nightly)"
MUTEX_SUFFIX = ""
elif (os.getenv("GIMP_RELEASE") and os.getenv("GIMP_UNSTABLE")) or os.getenv("GIMP_RC_VERSION"):
BUNDLE_IDENTIFIER = "org.gimp.gimp.seed"
BUNDLE_NAME = "GIMP (Beta)"
MUTEX_SUFFIX = f"-{os.getenv('GIMP_MUTEX_VERSION')}"
else:
BUNDLE_IDENTIFIER = "org.gimp.gimp"
BUNDLE_NAME = "GIMP"
MUTEX_SUFFIX = f"-{os.getenv('GIMP_MUTEX_VERSION')}"
new_text = text.replace("%BUNDLE_IDENTIFIER%", BUNDLE_IDENTIFIER)
new_text = new_text.replace("%BUNDLE_NAME%", BUNDLE_NAME)
new_text = new_text.replace("%GIMP_VERSION%", os.getenv("GIMP_VERSION"))
new_text = new_text.replace("%MUTEX_SUFFIX%", MUTEX_SUFFIX)
(GIMP_DISTRIB / "Info.plist").write_text(new_text)
### FIXME: Icon (generate Assets.car for Liquid Glass)
(GIMP_DISTRIB / "Resources").mkdir(parents=True, exist_ok=True)
shutil.copy2(Path(f"{os.getenv('MESON_BUILD_ROOT')}/gimp-data/images/logo/gimp.icns"), GIMP_DISTRIB / "Resources/AppIcon.icns")
## BUNDLE BASE (BARE MINIMUM TO RUN GTK APPS).
### FIXME: Needed for 'Send to Email' support (should be on sendmail.c source)
bundle(GIMP_SOURCE, "build/macos/patches/xdg-email", "--dest", "MacOS")
### Needed for file dialogs (only .compiled file is needed on macOS)
bundle(OPT_PREFIX, "share/glib-*/schemas/gschemas.compiled")
### Needed to open remote files
if os.path.exists(OPT_PREFIX / "bin/port"):
bundle(OPT_PREFIX, "lib/libproxy/libpxbackend*.dylib", "--dest", "Frameworks")
bundle(OPT_PREFIX, "lib/gio")
### Needed to not crash UI. See: https://gitlab.gnome.org/GNOME/gimp/-/issues/6165
bundle(OPT_PREFIX, "share/icons/Adwaita")
### Needed by GTK to use icon themes. See: https://gitlab.gnome.org/GNOME/gimp/-/issues/5080
bundle(GIMP_PREFIX, "share/icons/hicolor")
### Needed to loading icons in GUI
bundle(OPT_PREFIX, "lib/gdk-pixbuf-*/*/loaders/libpixbufloader*svg*")
bundle(OPT_PREFIX, "lib/gdk-pixbuf-*/*/loaders.cache")
loaders_cache = glob(f"{GIMP_DISTRIB}/Frameworks/gdk-pixbuf-*/*/loaders.cache")
text = Path(loaders_cache[0]).read_text()
new_text = text.replace(f"{OPT_PREFIX}/lib/", "")
Path(loaders_cache[0]).write_text(new_text)
### Needed for printing support
bundle(OPT_PREFIX, "lib/gtk-3.0/*.*.*/printbackends/*.so")
### Needed for macOS emoji keyboard support (with im-quartz.so)
bundle(OPT_PREFIX, "lib/gtk-3.0/*.*.*/immodules/*.so")
if os.path.exists(OPT_PREFIX / "bin/port"):
bundle(OPT_PREFIX, "etc/gtk-3.0/gtk.immodules", "--rename", "Frameworks/gtk-3.0/3.0.0/immodules.cache")
else: #os.path.exists(OPT_PREFIX / "bin/brew"):
bundle(OPT_PREFIX, "lib/gtk-3.0/*.*.*/immodules.cache")
im_cache = glob(f"{GIMP_DISTRIB}/Frameworks/gtk-3.0/*.*.*/immodules.cache")
text = Path(im_cache[0]).read_text()
new_text = re.sub(r'/.*?(?=gtk-3\.0/)', '', text)
Path(im_cache[0]).write_text(new_text)
### Needed for MacOS-style keyboard shortcuts
bundle(OPT_PREFIX, "share/themes/Mac")
## CORE FEATURES.
bundle(GIMP_PREFIX, "lib/libbabl*-*.*.*.dylib")
bundle(GIMP_PREFIX, "lib/babl-*")
bundle(GIMP_PREFIX, "lib/libgegl*-*.*.*.dylib")
bundle(GIMP_PREFIX, "lib/gegl-*")
bundle(GIMP_PREFIX, "lib/libgimp*-*.*.*.dylib")
bundle(GIMP_PREFIX, "lib/gimp")
bundle(GIMP_PREFIX, "share/gimp")
lang_array = [Path(f).stem for f in glob(str(Path(GIMP_SOURCE)/"po/*.po"))]
for lang in lang_array:
bundle(GIMP_PREFIX, f"share/locale/{lang}/LC_MESSAGES/*.mo")
# Needed for eventually used widgets, GTK inspector etc
if glob(f"{OPT_PREFIX}/share/locale/{lang}/LC_MESSAGES/gtk*.mo"):
bundle(OPT_PREFIX, f"share/locale/{lang}/LC_MESSAGES/gtk*.mo")
# For language list in text tool options
if glob(f"{OPT_PREFIX}/share/locale/{lang}/LC_MESSAGES/iso_639_3.mo"):
bundle(OPT_PREFIX, f"share/locale/{lang}/LC_MESSAGES/iso_639_3.mo")
bundle(GIMP_PREFIX, "etc/gimp")
## OTHER FEATURES AND PLUG-INS.
### Support for non .PAT patterns: https://gitlab.gnome.org/GNOME/gimp/-/issues/12351
bundle(OPT_PREFIX, "lib/gdk-pixbuf-*/*/loaders/libpixbufloader-bmp*")
bundle(OPT_PREFIX, "lib/gdk-pixbuf-*/*/loaders/libpixbufloader-gif*")
bundle(OPT_PREFIX, "lib/gdk-pixbuf-*/*/loaders/libpixbufloader-tiff*")
### FIXME: mypaint brushes (needs patching https://github.com/Homebrew/homebrew-core/pull/262039)
bundle(OPT_PREFIX, "share/mypaint-data/2.0")
### Needed for fontconfig
bundle(OPT_PREFIX, "etc/fonts")
#### Avoid writing in the system and avoid other programs breaking the cache
fonts_conf = GIMP_DISTRIB / "SharedSupport/fonts/fonts.conf"
text = fonts_conf.read_text()
new_text = text.replace(
f"{os.getenv('OPT_PREFIX')}/var",
f"~/Library/Application Support/GIMP/{os.getenv('GIMP_APP_VERSION')}"
)
fonts_conf.write_text(new_text)
### FIXME: Needed for 'th' word breaking in Text tool etc (https://github.com/macports/macports-ports/pull/30702 e https://github.com/Homebrew/homebrew-core/pull/262071)
#if os.path.exists(OPT_PREFIX / "bin/port"):
#bundle(OPT_PREFIX, "share/libthai")
### Needed for full CJK and Cyrillic support in file-pdf
bundle(OPT_PREFIX, "share/poppler")
#### Needed for signature support in file-pdf lib
if os.path.exists(OPT_PREFIX / "bin/port"):
bundle(OPT_PREFIX, "lib/nss/libssl3.dylib", "--dest", "Frameworks")
bundle(OPT_PREFIX, "lib/nss/libsmime3.dylib", "--dest", "Frameworks")
bundle(OPT_PREFIX, "lib/nss/libnssutil3.dylib", "--dest", "Frameworks")
bundle(OPT_PREFIX, "lib/nss/libnss3.dylib", "--dest", "Frameworks")
bundle(OPT_PREFIX, "lib/nspr/*.dylib", "--dest", "Frameworks")
### FIXME: Needed for file-ps work (but maintaining GS_LIB on app/main.c is tough)
if os.path.exists(OPT_PREFIX / "bin/port"):
bundle(OPT_PREFIX, "share/ghostscript/1*/iccprofiles/*.icc", "--dest", "Resources/ghostscript/iccprofiles")
bundle(OPT_PREFIX, "share/ghostscript/1*/Resource/Init/*", "--dest", "Resources/ghostscript/Resource/Init")
bundle(OPT_PREFIX, "share/ghostscript/1*/Resource/Font/*", "--dest", "Resources/ghostscript/Resource/Font")
else: #os.path.exists(OPT_PREFIX / "bin/brew"):
bundle(OPT_PREFIX, "share/ghostscript/iccprofiles/*.icc")
bundle(OPT_PREFIX, "share/ghostscript/Resource/Init")
bundle(OPT_PREFIX, "share/ghostscript/Resource/Font")
### Needed for file-wmf work
if os.path.exists(OPT_PREFIX / "bin/port"):
bundle(OPT_PREFIX, "share/fonts/libwmf/*", "--dest", "Resources/libwmf/fonts")
else: #os.path.exists(OPT_PREFIX / "bin/brew"):
bundle(OPT_PREFIX, "Cellar/libwmf/*/share/libwmf/fonts/*", "--dest", "Resources/libwmf/fonts")
### Needed for 'Show image graph'.
#### See: https://gitlab.gnome.org/GNOME/gimp/-/issues/6045
bundle(OPT_PREFIX, "bin/dot", "--dest", "MacOS")
#### See: https://gitlab.gnome.org/GNOME/gimp/-/issues/12119
bundle(OPT_PREFIX, "lib/graphviz/libgvplugin_dot*.dylib")
bundle(OPT_PREFIX, "lib/graphviz/libgvplugin_pango*.dylib")
bundle(OPT_PREFIX, "lib/graphviz/config*")
### Binaries for GObject Introspection support. See: https://gitlab.gnome.org/GNOME/gimp/-/issues/13170
bundle(GIMP_PREFIX, "lib/girepository-*/*.typelib")
bundle(OPT_PREFIX, "lib/libgirepository-*.dylib")
#### Python support
bundle(OPT_PREFIX, f"bin/python{os.getenv('PYTHON_VERSION')}", "--rename", "MacOS/python3")
if os.path.exists(OPT_PREFIX / "bin/port"):
bundle(OPT_PREFIX, f"Library/Frameworks/Python.framework/Versions/{os.getenv('PYTHON_VERSION')}", "--dest", "Frameworks/Python.framework/Versions")
else: #os.path.exists(OPT_PREFIX / "bin/brew"):
bundle(OPT_PREFIX, f"Frameworks/Python.framework/Versions/{os.getenv('PYTHON_VERSION')}", "--dest", "Frameworks/Python.framework/Versions")
bundle(OPT_PREFIX, f"lib/python{os.getenv('PYTHON_VERSION')}/site-packages/*", "--dest", f"Frameworks/Python.framework/Versions/{os.getenv('PYTHON_VERSION')}/lib/python{os.getenv('PYTHON_VERSION')}/site-packages")
clean(GIMP_DISTRIB, "Frameworks/Python.framework/*.pyc")
#####Needed for internet connection on python. See: https://gitlab.gnome.org/GNOME/gimp/-/issues/14722
pythonpath = Path(f"{GIMP_DISTRIB}/Frameworks/Python.framework/Versions/{os.getenv('PYTHON_VERSION')}/lib/python{os.getenv('PYTHON_VERSION')}")
for d in (pythonpath, pythonpath / "site-packages"):
sitecustomize = d / "sitecustomize.py"
code = """\nimport os\nimport certifi\n\n# Only set if not already configured by user\nif not os.environ.get('SSL_CERT_FILE'):\n os.environ['SSL_CERT_FILE'] = certifi.where()\n"""
if sitecustomize.exists() and code.strip() not in sitecustomize.read_text():
sitecustomize.write_text(sitecustomize.read_text() + code)
elif not sitecustomize.exists():
sitecustomize.write_text(code)
#####Needed since we use [[NSBundle mainBundle] bundlePath] on libgimpbase/gimpenv.c
real_path = Path(f"{GIMP_DISTRIB}/Resources/icons")
link_path = Path(f"{GIMP_DISTRIB}/Frameworks/Python.framework/Versions/{os.getenv('PYTHON_VERSION')}/Resources/Python.app/Contents/Resources/icons")
link_path.symlink_to(os.path.relpath(real_path, link_path.parent))
#### lua is buggy, and hard to bundle due to LUA_*PATH etc (see AppImage script)
#if os.path.exists(OPT_PREFIX / "bin/port"):
#bundle(OPT_PREFIX, "bin/luajit", "--dest", "MacOS")
#bundle(OPT_PREFIX, "lib/lua")
#bundle(OPT_PREFIX, "share/lua")
## MAIN EXECUTABLES AND DEPENDENCIES
### Minimal (and some additional) executables for the 'MacOS' folder
bundle(GIMP_PREFIX, "bin/gimp*")
if os.path.exists(OPT_PREFIX / "bin/brew"):
bundle(OPT_PREFIX, "Cellar/libarchive/*/lib/libarchive.*.dylib", "--dest", "Frameworks")
### Bundled just to promote GEGL. See: https://gitlab.gnome.org/GNOME/gimp/-/issues/10580
bundle(GIMP_PREFIX, "bin/gegl")
### Deps (DYLIBs) of the binaries in 'MacOS' and 'Frameworks' dirs
### We save the list of already copied DLLs to keep a state between 2_bundle-gimp-uni_dep runs.
done_dylib = Path(f"{os.getenv('MESON_BUILD_ROOT')}/done-dylib.list")
done_dylib.unlink(missing_ok=True)
for dir in ["MacOS", "Frameworks"]:
search_dir = GIMP_DISTRIB / dir
print(f"Searching for dependencies of {search_dir} in {GIMP_PREFIX} and {OPT_PREFIX}")
for dep in search_dir.rglob("*"):
if "Mach-O" in subprocess.run(["file", str(dep)], capture_output=True, text=True).stdout and ".dSYM" not in str(dep):
subprocess.run([
sys.executable, f"{GIMP_SOURCE}/tools/lib_bundle.py",
str(dep), f"{GIMP_PREFIX}/", f"{OPT_PREFIX}/",
str(GIMP_DISTRIB), "--output-dll-list", done_dylib
], check=True)
## .DSYM/DWARF DEBUG SYMBOLS (from babl, gegl and GIMP binaries)
for dir in ["MacOS", "Frameworks"]:
search_dir = GIMP_DISTRIB / dir
for binary in search_dir.rglob("*"):
if "Mach-O" in subprocess.run(["file", str(binary)], capture_output=True, text=True).stdout and ".dSYM" not in str(binary) and not binary.is_symlink():
result = subprocess.run(["dsymutil", "--no-output", binary], capture_output=True, text=True)
if not "no debug symbols" in result.stdout + result.stderr:
print(f"(INFO): generating debug symbols file as {binary}.dSYM")
try:
subprocess.run(["dsymutil", binary, "-o", f"{binary}.dSYM"], check=True, stderr=subprocess.DEVNULL)
except subprocess.CalledProcessError as e:
sys.stderr.write(f"Failed to generate debug symbols from {binary}: {e}\n")
## FIXME: DEVELOPMENT FILES (bundle babl, gegl and gimp libs as .framework with Headers/ ?).
#clean(GIMP_DISTRIB, "lib/*.a")
#bundle(GIMP_PREFIX, "include/gimp-*")
#bundle(GIMP_PREFIX, "include/babl-*")
#bundle(GIMP_PREFIX, "include/gegl-*")
#bundle(GIMP_PREFIX, "lib/pkgconfig/gimp*")
#bundle(GIMP_PREFIX, "lib/pkgconfig/babl*")
#bundle(GIMP_PREFIX, "lib/pkgconfig/gegl*")

43
build/macos/Info.plist Normal file
View file

@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>gimp%MUTEX_SUFFIX%</string>
<key>CFBundleIconFile</key>
<string>AppIcon.icns</string>
<key>CFBundleIdentifier</key>
<string>%BUNDLE_IDENTIFIER%</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>%BUNDLE_NAME%</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleSupportedPlatforms</key>
<array>
<string>MacOSX</string>
</array>
<key>CFBundleShortVersionString</key>
<string>%GIMP_VERSION%</string>
<key>CFBundleSignature</key>
<string>GIMP</string>
<key>CFBundleVersion</key>
<string>%GIMP_VERSION%</string>
<key>NSHumanReadableCopyright</key>
<string>© The GIMP Team</string>
<key>LSApplicationCategoryType</key>
<string>public.app-category.photography</string>
<!--keep LSMinimumSystemVersion consistent with devel-docs/os-support.txt-->
<key>LSMinimumSystemVersion</key>
<string>14.0</string>
<key>NSHighResolutionCapable</key>
<true/>
<key>NSRequiresAquaSystemAppearance</key>
<false/>
<key>NLSRequiresNativeExecution</key>
<true/>
</dict>
</plist>

View file

@ -1,5 +1,7 @@
## macOS files not here
## Note about macOS build situation
macOS build files are not stored here yet.
These macOS build files on GIMP repo do not fully support older macOS yet.
You can use them only the latest 3 versions of macOS.
They are in: [gimp-macos-build](https://gitlab.gnome.org/Infrastructure/gimp-macos-build) repo.
The build files for older macOS are in: [gimp-macos-build]
(https://gitlab.gnome.org/Infrastructure/gimp-macos-build) repo.

View file

@ -0,0 +1,50 @@
# For easier quality of life and to avoid disparity between pacakges,
# KEEP THIS FILE ORDERING SIMILAR to build/windows/all-deps-uni.txt
# BUILD-TIME ONLY DEPS
gettext \
gi-docgen \
gobject-introspection +quartz -x11|homebrew:gobject-introspection \
libxslt|homebrew:libxslt \
meson|homebrew:meson \
pkgconfig|homebrew:pkgconf \
vala -valadoc \
# RUNTIME DEPS
aalib -x11 \
appstream -x11|homebrew:appstream \
atk \
cairo +quartz -x11|homebrew:cairo \
#cfitsio not built by Apple Clang
gexiv2|homebrew:gexiv2 \
ghostscript -x11|homebrew:ghostscript \
glib2 +quartz -x11|homebrew:glib \
glib-networking|homebrew:glib-networking \
graphviz -x11|homebrew:graphviz \
gtk3 +quartz -x11|homebrew:gtk+3 \ adwaita-icon-theme +quartz -x11|homebrew:adwaita-icon-theme \ shared-mime-info|homebrew:shared-mime-info \
iso-codes|homebrew:iso-codes \
json-glib|homebrew:json-glib \
lcms2|homebrew:lcms2 \
libarchive|homebrew:libarchive \
libheif \
libjpeg-turbo|homebrew:libjpeg-turbo \
libjxl \
libmng \
libmypaint \
libpng|homebrew:libpng \
librsvg|homebrew:librsvg \
tiff \
webp \
libwmf -x11|homebrew:libwmf \
#maxflow not available yet
mypaint-brushes|homebrew:mypaint-brushes \
openexr \
openjpeg \
pango +quartz -x11 \
poppler|homebrew:poppler \
poppler-data \
python313|homebrew:python \ py313-certifi|homebrew:certifi
py313-gobject3|homebrew:pygobject3 \
qoi \
#suitesparse not built by Apple Clang
#xpm depends on x11

View file

@ -0,0 +1,191 @@
From fc8868bdd2900e6bc1399dc336d73ac1c27e60f5 Mon Sep 17 00:00:00 2001
From: Bruno Lopes <brunvonlope@outlook.com>
Date: Tue, 6 Jan 2026 09:59:35 -0300
Subject: [PATCH] app, libgimpwidgets, meson, plug-ins: Patch macOS bundle
This is needed because our current code assumes that
the prefix is .app/Contents/Resources, which should not.
---
app/config/gimpcoreconfig.c | 2 +-
app/main.c | 39 +++++++++++++---------------
libgimpbase/gimpenv.c | 12 +++++----
libgimpwidgets/gimpwidgets-private.c | 2 +-
meson.build | 2 +-
plug-ins/common/file-wmf.c | 4 +--
6 files changed, 30 insertions(+), 31 deletions(-)
diff --git a/app/config/gimpcoreconfig.c b/app/config/gimpcoreconfig.c
index a4591055d6..853cf99f10 100644
--- a/app/config/gimpcoreconfig.c
+++ b/app/config/gimpcoreconfig.c
@@ -311,7 +311,7 @@ gimp_core_config_class_init (GimpCoreConfigClass *klass)
#ifdef ENABLE_RELOCATABLE_RESOURCES
mypaint_brushes = g_build_filename ("${gimp_installation_dir}",
- "share", "mypaint-data",
+ "Resources", "mypaint-data",
"2.0", "brushes", NULL);
#else
mypaint_brushes = g_strdup (MYPAINT_BRUSHES_DIR);
diff --git a/app/main.c b/app/main.c
index d577dd0b04..b0c326ac5c 100644
--- a/app/main.c
+++ b/app/main.c
@@ -355,13 +355,13 @@ gimp_macos_setenv (const char * progname)
gboolean need_pythonhome = TRUE;
bin_dir = g_path_get_dirname (resolved_path);
- tmp = g_strdup_printf ("%s/../Resources/lib", bin_dir);
+ tmp = g_strdup_printf ("%s/../Frameworks", bin_dir);
lib_dir = g_canonicalize_filename (tmp, NULL);
g_free (tmp);
- tmp = g_strdup_printf ("%s/../Resources/share", bin_dir);
+ tmp = g_strdup_printf ("%s/../Resources", bin_dir);
share_dir = g_canonicalize_filename (tmp, NULL);
g_free (tmp);
- tmp = g_strdup_printf ("%s/../Resources/etc", bin_dir);
+ tmp = g_strdup_printf ("%s/../SharedSupport", bin_dir);
etc_dir = g_canonicalize_filename (tmp, NULL);
g_free (tmp);
@@ -392,23 +392,6 @@ gimp_macos_setenv (const char * progname)
}
}
- /* Detect we were built in homebrew for MacOS (for PYTHONHOME purposes) */
- tmp = g_strdup_printf ("%s/../Frameworks/Python.framework", share_dir);
- if (tmp && !stat (tmp, &sb) && S_ISDIR (sb.st_mode))
- {
- g_print ("GIMP was built with homebrew\n");
- need_pythonhome = FALSE;
- }
- g_free (tmp);
- /* Detect we were built in MacPorts for MacOS (for PYTHONHOME purposes) */
- tmp = g_strdup_printf ("%s/../Library/Frameworks/Python.framework", share_dir);
- if (tmp && !stat (tmp, &sb) && S_ISDIR (sb.st_mode))
- {
- g_print ("GIMP was built with MacPorts\n");
- need_pythonhome = FALSE;
- }
- g_free (tmp);
-
/* Minimum runtime paths */
path_len = strlen (g_getenv ("PATH") ? g_getenv ("PATH") : "") + strlen (bin_dir) + 2;
path = g_try_malloc (path_len);
@@ -461,6 +444,20 @@ gimp_macos_setenv (const char * progname)
g_setenv ("GTK_IM_MODULE", tmp, TRUE);
g_free (tmp);
+ /* Packaging-specific paths */
+ tmp = g_strdup_printf ("%s/%s/%s", lib_dir, GIMP_PACKAGE, GIMP_PLUGIN_VERSION);
+ g_setenv ("GIMP3_PLUGINDIR", tmp, TRUE);
+ g_free (tmp);
+ tmp = g_strdup_printf ("%s/%s/%s", share_dir, GIMP_PACKAGE, GIMP_DATA_VERSION);
+ g_setenv ("GIMP3_DATADIR", tmp, TRUE);
+ g_free (tmp);
+ tmp = g_strdup_printf ("%s/locale", share_dir);
+ g_setenv ("GIMP3_LOCALEDIR", tmp, TRUE);
+ g_free (tmp);
+ tmp = g_strdup_printf ("%s/%s/%s", etc_dir, GIMP_PACKAGE, GIMP_SYSCONF_VERSION);
+ g_setenv ("GIMP3_SYSCONFDIR", tmp, TRUE);
+ g_free (tmp);
+
/* Other needed runtime paths (related to features) */
tmp = g_strdup_printf ("%s/fonts", etc_dir);
g_setenv ("FONTCONFIG_PATH", tmp, TRUE);
@@ -479,7 +476,7 @@ gimp_macos_setenv (const char * progname)
g_free (tmp);
if (need_pythonhome)
{
- tmp = g_strdup_printf ("%s/Library/Frameworks/Python.framework/Versions/%s", share_dir, PYTHON_VERSION);
+ tmp = g_strdup_printf ("%s/Python.framework/Versions/%s", lib_dir, PYTHON_VERSION);
g_setenv ("PYTHONHOME", tmp, TRUE);
g_free (tmp);
}
diff --git a/libgimpbase/gimpenv.c b/libgimpbase/gimpenv.c
index 0af4f2f653..7293fe4bb1 100644
--- a/libgimpbase/gimpenv.c
+++ b/libgimpbase/gimpenv.c
@@ -415,17 +415,18 @@ gimp_installation_directory (void)
{
NSAutoreleasePool *pool;
- NSString *resource_path;
+ NSString *app_path;
+ gchar *app_gpath;
gchar *basename;
gchar *basepath;
gchar *dirname;
pool = [[NSAutoreleasePool alloc] init];
- resource_path = [[NSBundle mainBundle] resourcePath];
+ app_path = [[NSBundle mainBundle] bundlePath];
- basename = g_path_get_basename ([resource_path UTF8String]);
- basepath = g_path_get_dirname ([resource_path UTF8String]);
+ basename = g_path_get_basename ([app_path UTF8String]);
+ basepath = g_path_get_dirname ([app_path UTF8String]);
dirname = g_path_get_basename (basepath);
if (! strcmp (basename, ".libs"))
@@ -488,7 +489,8 @@ gimp_installation_directory (void)
{
/* if none of the above match, we assume that we are really in a bundle */
- toplevel = g_strdup ([resource_path UTF8String]);
+ app_gpath = g_strdup ([app_path UTF8String]);
+ toplevel = g_strconcat (app_gpath, "/Contents", NULL);
}
g_free (basename);
diff --git a/libgimpwidgets/gimpwidgets-private.c b/libgimpwidgets/gimpwidgets-private.c
index 0ebe7fa7e0..c7ab6d6950 100644
--- a/libgimpwidgets/gimpwidgets-private.c
+++ b/libgimpwidgets/gimpwidgets-private.c
@@ -97,7 +97,7 @@ gimp_widgets_init (GimpHelpFunc standard_help_func,
{
cat_dir = "apps";
#ifdef ENABLE_RELOCATABLE_RESOURCES
- base_dir = g_build_filename (gimp_installation_directory (), "share", "icons", "hicolor", NULL);
+ base_dir = g_build_filename (gimp_installation_directory (), "Resources", "icons", "hicolor", NULL);
#else
base_dir = g_build_filename (DATAROOTDIR, "icons", "hicolor", NULL);
#endif
diff --git a/meson.build b/meson.build
index 9614be6197..b18955cdf9 100644
--- a/meson.build
+++ b/meson.build
@@ -536,7 +536,7 @@ mypaint_brushes = dependency('mypaint-brushes-2.0')
if relocatable_bundle
mypaint_brushes_dir = '${gimp_installation_dir}'\
- /'share'/'mypaint-data'/'2.0'/'brushes'
+ /'Resources'/'mypaint-data'/'2.0'/'brushes'
else
mypaint_brushes_dir = mypaint_brushes.get_variable(pkgconfig: 'brushesdir')
endif
diff --git a/plug-ins/common/file-wmf.c b/plug-ins/common/file-wmf.c
index 62c33ab221..b7517e0624 100644
--- a/plug-ins/common/file-wmf.c
+++ b/plug-ins/common/file-wmf.c
@@ -382,7 +382,7 @@ load_wmf_size (GFile *file,
#ifdef ENABLE_RELOCATABLE_RESOURCES
wmffontdirs[0] = g_build_filename (gimp_installation_directory (),
- "share/libwmf/fonts", NULL);
+ "Resources/libwmf/fonts", NULL);
flags |= WMF_OPT_FONTDIRS;
api_options.fontdirs = wmffontdirs;
#endif
@@ -529,7 +529,7 @@ wmf_load_file (GFile *file,
#ifdef ENABLE_RELOCATABLE_RESOURCES
wmffontdirs[0] = g_build_filename (gimp_installation_directory (),
- "share/libwmf/fonts/", NULL);
+ "Resources/libwmf/fonts/", NULL);
flags |= WMF_OPT_FONTDIRS;
api_options.fontdirs = wmffontdirs;
#endif
--
2.50.1 (Apple Git-155)

View file

@ -0,0 +1,49 @@
From 2dd50107f6a700527741eb2373a8ba26975a961c Mon Sep 17 00:00:00 2001
From: Bruno Lopes <brunvonlope@outlook.com>
Date: Fri, 26 Dec 2025 19:54:22 -0300
Subject: [PATCH] build/macos: Do not require gexiv2-0.14 on homebrew
---
libgimp/meson.build | 2 +-
meson.build | 6 ++----
2 files changed, 3 insertions(+), 5 deletions(-)
diff --git a/libgimp/meson.build b/libgimp/meson.build
index c12e60c2cd..738564c2b9 100644
--- a/libgimp/meson.build
+++ b/libgimp/meson.build
@@ -387,7 +387,7 @@ libgimp_deps_table = [
{ 'gir': 'cairo-1.0', 'vapi': 'cairo-1.0', },
{ 'gir': 'GdkPixbuf-2.0', 'vapi': 'gdk-pixbuf-2.0', },
{ 'gir': 'Gegl-0.4', 'vapi': 'gegl-0.4', },
- { 'gir': 'GExiv2-0.10', 'vapi': 'gexiv2', },
+ { 'gir': 'GExiv2-0.16', 'vapi': 'gexiv2-0.16', },
{ 'gir': 'Gio-2.0', 'vapi': 'gio-2.0', },
{ 'gir': gio_specific_gir, 'vapi': gio_specific_vapi, },
{ 'gir': 'GLib-2.0', 'vapi': 'glib-2.0', },
diff --git a/meson.build b/meson.build
index 97662d62d7..4fcf576cce 100644
--- a/meson.build
+++ b/meson.build
@@ -454,9 +454,8 @@ gegl_minver = '0.4.66'
gegl = dependency('gegl-0.4', version: '>='+gegl_minver)
exiv2_minver = '0.27.4'
exiv2 = dependency('exiv2', version: '>='+exiv2_minver)
-gexiv2_minver = '0.14.0'
-gexiv2_maxver = '0.15.0'
-gexiv2 = dependency('gexiv2', version: ['>='+gexiv2_minver, '<'+gexiv2_maxver])
+gexiv2_minver = '0.16.0'
+gexiv2 = dependency('gexiv2-0.16', version: '>='+gexiv2_minver)
gio = dependency('gio-2.0')
gio_specific_name = platform_windows ? 'gio-windows-2.0' : 'gio-unix-2.0'
@@ -1686,7 +1685,6 @@ install_conf.set('GDK_PIXBUF_REQUIRED_VERSION', gdk_pixbuf_minver)
install_conf.set('GEGL_REQUIRED_VERSION', gegl_minver)
install_conf.set('EXIV2_REQUIRED_VERSION', exiv2_minver)
install_conf.set('GEXIV2_REQUIRED_VERSION', gexiv2_minver)
-install_conf.set('GEXIV2_MAX_VERSION', gexiv2_maxver)
install_conf.set('GLIB_REQUIRED_VERSION', glib_minver)
install_conf.set('GTK_REQUIRED_VERSION', gtk3_minver)
install_conf.set('HARFBUZZ_REQUIRED_VERSION', harfbuzz_minver)
--
2.50.1 (Apple Git-155)

View file

@ -0,0 +1,24 @@
From 9ee6b4e10c0b429358640aac4e081e2aca329ddb Mon Sep 17 00:00:00 2001
From: Bruno Lopes <brunvonlope@outlook.com>
Date: Sat, 10 Jan 2026 15:49:22 -0300
Subject: [PATCH] meson: Patch python version
---
meson.build | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/meson.build b/meson.build
index cd51137d6f..b59fb6cceb 100644
--- a/meson.build
+++ b/meson.build
@@ -130,7 +130,7 @@ versionconfig.set('GIMP_API_VERSION', gimp_api_version)
pkgconfig = import('pkgconfig')
i18n = import('i18n')
gnome = import('gnome')
-python = import('python').find_installation()
+python = import('python').find_installation('python3.13')
simd = import('unstable-simd')
fs = import('fs')
--
2.50.1 (Apple Git-155)

View file

@ -0,0 +1,67 @@
#!/bin/sh
# this is poor man xdg-email wrapper for osx.
# It is intended to emulate xdg-email functionality using Mail.app and oascript.
# currently only --attach --subject and --body commands are supported
# and only first email address is used
# There is now ling args support in osx getopt, so we are doing this hack
# and transforming long options to short ones
#
# Written by Alex Samorukov, samm@os2.kiev.ua
for arg in "$@"; do
shift
case "$arg" in
"--attach") set -- "$@" "-a" ;;
"--subject") set -- "$@" "-s" ;;
"--body") set -- "$@" "-b" ;;
"--help") set -- "$@" "-h" ;;
*) set -- "$@" "$arg"
esac
done
# Default behavior
rest=false; ws=false
ATTACH=""
SUBJECT=""
BODY=""
# Parse short options
OPTIND=1
while getopts "a:s:b:h" opt
do
case "$opt" in
"a") ATTACH="$OPTARG"; ;;
"s") SUBJECT="$OPTARG"; ;;
"b") BODY="$OPTARG"; ;;
"h") echo "sends email using Mail.app from commandline"; exit 1 ;;
esac
done
shift $(expr $OPTIND - 1) # remove options from positional parameters
TO_ADDR="$1"
SCRIPT="
set recipientName to \"\"
set recipientAddress to \"${TO_ADDR}\"
set theSubject to \"${SUBJECT}\"
set theContent to \"${BODY}\"
set theAttachmentFile to \"${ATTACH}\" -- the attachment path
tell application \"Mail\"
## set focus
activate
## Create the message
set theMessage to make new outgoing message with properties {subject:theSubject, content:theContent, visible:true}
## attach file
tell theMessage to make new attachment with properties {file name:theAttachmentFile}
## Set a recipient
tell theMessage
make new to recipient with properties {name:recipientName, address:recipientAddress}
end tell
end tell
"
echo "$SCRIPT"|/usr/bin/osascript

View file

@ -2143,6 +2143,12 @@ if get_option('windows-installer') or get_option('ms-store')
meson.add_install_script(install_win_bundling_script)
endif
# On macOS, install deps in an .app bundle before distributing
if get_option('dmg')
install_macos_debug_script = find_program('build/macos/2_bundle-gimp-uni_base.py')
meson.add_install_script(install_macos_debug_script)
endif
################################################################################
# Subdir installations