diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml
index 80b3b48a..5ff73b33 100644
--- a/.github/workflows/test.yaml
+++ b/.github/workflows/test.yaml
@@ -5,8 +5,8 @@ on:
- pull_request
jobs:
- linux:
- name: Linux
+ linux-autotools:
+ name: Linux Autotools
runs-on: ubuntu-22.04
strategy:
matrix:
@@ -35,3 +35,35 @@ jobs:
make
- name: "Run tests"
run: make distcheck
+
+ linux-meson:
+ name: Linux Meson
+ runs-on: ubuntu-22.04
+ strategy:
+ matrix:
+ configureFlags:
+ - ""
+ - "-Dglib=enabled -Dintrospection=enabled"
+ - "-Dgegl=enabled"
+ include:
+ - configureFlags: "-Dglib=enabled -Dintrospection=enabled"
+ extraDeps: "libgirepository1.0-dev"
+ - configureFlags: "-Dgegl=enabled"
+ extraDeps: "libgegl-dev"
+ steps:
+ - uses: actions/checkout@v4
+ - name: "Install dependencies"
+ run: |
+ sudo apt-get update
+ sudo apt-get install -y \
+ libjson-c-dev \
+ meson \
+ ninja-build \
+ gettext \
+ ${{ matrix.extraDeps }}
+ - name: "Build"
+ run: |
+ meson setup _build --buildtype=release -Dauto_features=disabled
+ meson compile -C _build
+ - name: "Run tests"
+ run: meson dist -C _build
diff --git a/README.md b/README.md
index 0143fd07..96f38781 100644
--- a/README.md
+++ b/README.md
@@ -12,17 +12,16 @@ License: ISC, see [COPYING](./COPYING) for details.
## Dependencies
* All configurations and builds:
+ - C compiler, linker etc.
+ - [Python](http://python.org/)
+ - [Meson](https://en.wikipedia.org/wiki/Meson_(software)) and [ninja](https://en.wikipedia.org/wiki/Ninja_(build_system))
+ - Alternately, Autotools can be used instead but they are now considered deprecated.
- [json-c](https://github.com/json-c/json-c/wiki) (>= 0.11)
- - C compiler, `make` etc.
-* Most configurations (all except `--disable-introspection --without-glib`):
+ - [gettext](https://www.gnu.org/software/gettext/gettext.html) (unless `-Di18n=disabled`)
+* Most configurations (all except `-Dintrospection=disabled -Dglib=disabled`):
- [GObject-Introspection](https://live.gnome.org/GObjectIntrospection)
- [GLib](https://wiki.gnome.org/Projects/GLib)
-* When building from `git` (developer package names vary by distribution):
- - [Python](http://python.org/)
- - [autotools](https://en.wikipedia.org/wiki/GNU_Build_System)
- - [intltool](https://freedesktop.org/wiki/Software/intltool/)
- - [gettext](https://www.gnu.org/software/gettext/gettext.html)
-* For `--enable-gegl` (GIMP *does not* require this):
+* For `-Dgegl=enabled` (GIMP *does not* require this):
- [GEGL + BABL](http://gegl.org/)
### Install dependencies (Debian and derivatives)
@@ -30,10 +29,10 @@ License: ISC, see [COPYING](./COPYING) for details.
On recent Debian-like systems, you can type the following
to get started with a standard configuration:
- # apt install -y build-essential
+ # apt install -y build-essential meson
# apt install -y libjson-c-dev libgirepository1.0-dev libglib2.0-dev
-When building from git:
+Additionally, when using the deprecated Autotools build system:
# apt install -y python autotools-dev intltool gettext libtool
@@ -44,11 +43,11 @@ You might also try using your package manager:
### Install dependencies (Red Hat and derivatives)
-The following works on a minimal CentOS 7 installation:
+The following should works on a minimal CentOS 7 installation:
- # yum install -y gcc gobject-introspection-devel json-c-devel glib2-devel
+ # yum install -y gcc meson gobject-introspection-devel json-c-devel glib2-devel
-When building from git, you'll want to add:
+Additionally, when using the deprecated Autotools build system:
# yum install -y git python autoconf intltool gettext libtool
@@ -60,9 +59,9 @@ You might also try your package manager:
Works with a fresh OpenSUSE Tumbleweed Docker image:
- # zypper install gcc13 gobject-introspection-devel libjson-c-devel glib2-devel
+ # zypper install gcc13 meson gobject-introspection-devel libjson-c-devel glib2-devel
-When building from git:
+Additionally, when using the deprecated Autotools build system:
# zypper install git python311 autoconf intltool gettext-tools libtool
@@ -72,61 +71,53 @@ Package manager:
## Build and install
+You can use [Meson](https://mesonbuild.com/) build system.
+
+ $ meson setup _build --prefix=/usr
+ $ meson compile -C _build
+ # meson install -C _build
+ # ldconfig
+
MyPaint and libmypaint benefit dramatically from autovectorization and other compiler optimizations.
-You may want to set your CFLAGS before compiling (for gcc):
+You may want to set your CFLAGS before compiling (for gcc) by passing something like the following as an argument to `meson setup`:
- $ export CFLAGS='-Ofast -ftree-vectorize -fopt-info-vec-optimized -march=native -mtune=native -funsafe-math-optimizations -funsafe-loop-optimizations'
+ -Dc_args='-Ofast -ftree-vectorize -fopt-info-vec-optimized -march=native -mtune=native -funsafe-math-optimizations -funsafe-loop-optimizations'
-The traditional setup works just fine.
+Alternately, you can use the traditional Autotools build system for now (it is deprecated and will be eventually removed):
- $ ./autogen.sh # Only needed when building from git.
+ $ ./autogen.sh
$ ./configure
# make install
# ldconfig
-### Maintainer mode
-
-We don't ship a `configure` script in our git repository. If you're
-building from git, you have to kickstart the build environment with:
-
- $ git clone https://github.com/mypaint/libmypaint.git
- $ cd libmypaint
- $ ./autogen.sh
-
-This script generates `configure` from `configure.ac`, after running a
-few checks to make sure your build environment is broadly OK. It also
-regenerates certain important generated headers if they need it.
-
-Folks building from a release tarball don't need to do this: they will
-have a `configure` script from the start.
-
### Configure
- $ ./configure
- $ ./configure --prefix=/tmp/junk/example
+Meson requires out-of-tree builds so you need to specify a build directory.
-There are several MyPaint-specific options.
-These can be shown by running
+ $ meson setup _build
+ $ meson setup _build --prefix=/tmp/junk/example
- $ ./configure --help
+In addition to to [Meson options](https://mesonbuild.com/Builtin-options.html#compiler-options), there are several libmypaint-specific options, see `meson_options.txt` file for details.
+
+ $ meson setup _build -Dgegl=disabled -Ddocs=true -Di18n=enabled
### Build
- $ make
+ $ meson compile -C _build
Once MyPaint is built, you can run the test suite and/or install it.
### Test
- $ make check
+ $ meson test -C _build
This runs all the unit tests.
### Install
- # make install
+ # meson install -C _build
-Uninstall libmypaint with `make uninstall`.
+Uninstall libmypaint with `meson uninstall -C _build`.
### Check availability
@@ -172,11 +163,7 @@ for details of how you can begin contributing.
The distribution release can be generated with:
- $ make dist
-
-And it should be checked before public release with:
-
- $ make distcheck
+ $ meson dist -C _build
## Localization
@@ -198,7 +185,7 @@ in `po/POTFILES.in`.
You can update the .po files when translated strings in the code change
using:
- $ cd po && make update-po
+ $ meson compile -C _build update-po
When the results of this are pushed, Weblate translators will see the
new strings immediately.
diff --git a/appveyor_build.sh b/appveyor_build.sh
index 88c15a9b..6d61fd00 100644
--- a/appveyor_build.sh
+++ b/appveyor_build.sh
@@ -11,7 +11,9 @@ pacman --noconfirm -S --needed \
base-devel \
${PKG_PREFIX}-json-c \
${PKG_PREFIX}-glib2 \
- ${PKG_PREFIX}-gobject-introspection
+ ${PKG_PREFIX}-gobject-introspection \
+ ${PKG_PREFIX}-meson \
+ git
# Add m4 directories to the ACLOCAL_PATH
@@ -30,6 +32,11 @@ done
export ACLOCAL_PATH
export PWD="$APPVEYOR_BULD_FOLDER"
+# Check that Meson build works.
+meson setup _build --buildtype=release -Dgegl=false -Ddocs=false
+meson dist -C _build
+rm -rf _build
+
./autogen.sh
./configure
make distcheck
diff --git a/build-aux/fix-po-location.py b/build-aux/fix-po-location.py
new file mode 100755
index 00000000..8d649723
--- /dev/null
+++ b/build-aux/fix-po-location.py
@@ -0,0 +1,21 @@
+#!/usr/bin/env python3
+
+"""
+The `generate.py` script will pass location info as a translator note.
+This script converts it to a proper location comment.
+"""
+
+import re
+import sys
+
+LOCATION_PATTERN = re.compile(r"^#\. (: \.\./brushsettings.json:.*)", re.MULTILINE)
+
+match sys.argv:
+ case [_, input_path, output_path]:
+ pass
+ case _:
+ print("usage: fix-po-location.py ", file=sys.stderr)
+ sys.exit(1)
+
+with open(input_path) as po_in, open(output_path, "w") as po_out:
+ po_out.write(LOCATION_PATTERN.sub(r"#\1", po_in.read()))
diff --git a/configure.ac b/configure.ac
index b6e1e060..85d2e740 100644
--- a/configure.ac
+++ b/configure.ac
@@ -296,12 +296,19 @@ fi
AM_CONDITIONAL(ENABLE_GEGL, test "x$enable_gegl" = "xyes")
AC_SUBST(DOXYGEN_EXCLUDED)
+DOXYXML_BUILD_PATH="$PWD/doc"
+AC_SUBST(DOXYXML_BUILD_PATH)
+
+DOXYGEN_SOURCE_ROOT="$PWD"
+AC_SUBST(DOXYGEN_SOURCE_ROOT)
+
# Set pkg-config variables before generation.
AC_SUBST(PKG_CONFIG_REQUIRES)
AC_CONFIG_FILES([
doc/Doxyfile
doc/Makefile
+ doc/source/conf.py
gegl/libmypaint-gegl-]libmypaint_api_platform_version()[.pc:gegl/libmypaint-gegl.pc.in
gegl/Makefile
libmypaint-]libmypaint_api_platform_version()[.pc:libmypaint.pc.in
diff --git a/doc/.gitignore b/doc/.gitignore
index e88d994a..8cb71b38 100644
--- a/doc/.gitignore
+++ b/doc/.gitignore
@@ -1,3 +1,4 @@
+source/conf.py
Doxyfile
doxygen/
build/
diff --git a/doc/Doxyfile.in b/doc/Doxyfile.in
index e48274ef..628e2e96 100644
--- a/doc/Doxyfile.in
+++ b/doc/Doxyfile.in
@@ -38,7 +38,7 @@ PROJECT_NAME = "libmypaint"
# could be handy for archiving the generated documentation or if some version
# control system is used.
-PROJECT_NUMBER = 0.1
+PROJECT_NUMBER = @LIBMYPAINT_VERSION@
# Using the PROJECT_BRIEF tag one can provide an optional one line description
# for a project that appears at the top of each page and should give viewer a
@@ -58,7 +58,7 @@ PROJECT_LOGO =
# entered, it will be relative to the location where doxygen was started. If
# left blank the current directory will be used.
-OUTPUT_DIRECTORY =
+OUTPUT_DIRECTORY = @DOXYXML_BUILD_PATH@
# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub-
# directories (in 2 levels) under the output directory of each output format and
@@ -819,7 +819,7 @@ WARN_LOGFILE =
# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
# Note: If this tag is empty the current directory is searched.
-INPUT = ../..
+INPUT = @DOXYGEN_SOURCE_ROOT@
# This tag can be used to specify the character encoding of the source files
# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
diff --git a/doc/Makefile.am b/doc/Makefile.am
index eb21386a..5b994fbd 100644
--- a/doc/Makefile.am
+++ b/doc/Makefile.am
@@ -1,5 +1,4 @@
EXTRA_DIST = \
- source/conf.py \
source/index.rst
if ENABLE_DOCS
diff --git a/doc/meson.build b/doc/meson.build
new file mode 100644
index 00000000..54cb3c65
--- /dev/null
+++ b/doc/meson.build
@@ -0,0 +1,37 @@
+doc_conf = configuration_data()
+doc_conf.merge_from(conf)
+
+doc_conf.set('DOXYGEN_SOURCE_ROOT', meson.project_source_root())
+doc_conf.set('DOXYXML_BUILD_PATH', meson.current_build_dir())
+doc_conf.set('DOXYGEN_EXCLUDED', '')
+
+doxyfile = configure_file(
+ input: 'Doxyfile.in',
+ output: 'Doxyfile',
+ configuration: doc_conf,
+)
+
+doxygen_index = custom_target(
+ 'doxygen',
+ input: doxyfile,
+ output: 'index.xml',
+ command: [
+ doxygen,
+ '@INPUT@',
+ ],
+)
+
+subdir('source')
+
+run_target(
+ 'sphinx',
+ depends: [
+ doxygen_index,
+ ],
+ command: [
+ sphinx_build,
+ '-c', meson.current_build_dir() / 'source',
+ meson.current_source_dir() / 'source',
+ meson.current_build_dir() / 'build',
+ ],
+)
diff --git a/doc/source/conf.py b/doc/source/conf.py.in
similarity index 98%
rename from doc/source/conf.py
rename to doc/source/conf.py.in
index ee1b12f4..5b2f1ae8 100644
--- a/doc/source/conf.py
+++ b/doc/source/conf.py.in
@@ -37,7 +37,7 @@
# Breathe setup, for integrating doxygen content
extensions.append('breathe')
-doxyxml_dir = os.path.join(os.path.abspath(os.path.dirname(__file__)), '../doxygen')
+doxyxml_dir = '@DOXYXML_BUILD_PATH@'
print(doxyxml_dir)
breathe_projects = {"libmypaint": doxyxml_dir}
breathe_default_project = "libmypaint"
@@ -63,9 +63,9 @@
# built documents.
#
# The short X.Y version.
-version = '0.1'
+version = '@LIBMYPAINT_VERSION@'
# The full version, including alpha/beta/rc tags.
-release = '0.1'
+release = '@LIBMYPAINT_VERSION_FULL@'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
diff --git a/doc/source/meson.build b/doc/source/meson.build
new file mode 100644
index 00000000..d21b005c
--- /dev/null
+++ b/doc/source/meson.build
@@ -0,0 +1,5 @@
+sphinx_conf_file = configure_file(
+ input: 'conf.py.in',
+ output: 'conf.py',
+ configuration: doc_conf,
+)
diff --git a/gegl/meson.build b/gegl/meson.build
new file mode 100644
index 00000000..fc4adc31
--- /dev/null
+++ b/gegl/meson.build
@@ -0,0 +1,66 @@
+libmypaint_gegl_inc = include_directories('.')
+
+libmypaint_gegl_sources = [
+ '../glib/mypaint-gegl-glib.c',
+ 'mypaint-gegl-surface.c',
+]
+
+libmypaint_gegl_headers = [
+ '../glib/mypaint-gegl-glib.h',
+ 'mypaint-gegl-surface.h',
+]
+
+libmypaint_gegl = library(
+ f'mypaint-gegl-@api_platform_version@',
+ libmypaint_gegl_sources,
+ include_directories: toplevel_inc,
+ link_with: libmypaint,
+ dependencies: [
+ json,
+ gobject,
+ gegl,
+ ],
+ version: abi_version_info,
+ install: true,
+)
+
+install_headers(
+ libmypaint_gegl_headers,
+ subdir: 'libmypaint-gegl',
+)
+
+
+if use_introspection
+ gnome = import('gnome')
+
+ libmypaint_gegl_gir = gnome.generate_gir(
+ libmypaint_gegl,
+ namespace: 'MyPaintGegl',
+ nsversion: api_platform_version,
+
+ sources: libmypaint_gegl_sources + libmypaint_gegl_headers,
+ symbol_prefix: 'mypaint_gegl',
+ identifier_prefix: 'MyPaintGegl',
+
+ includes: [
+ 'GObject-2.0',
+ gegl_gir,
+ libmypaint_gir[0],
+ ],
+ install: true,
+ )
+endif
+
+
+pkgconfig.generate(
+ libmypaint_gegl,
+ name: meson.project_name() + '-gegl-' + api_platform_version,
+ version: version_full,
+ description: 'MyPaint brush engine library, with GEGL integration',
+ requires: [
+ libmypaint,
+ gegl,
+ ],
+ url: project_url,
+ subdirs: 'libmypaint-gegl',
+)
diff --git a/meson.build b/meson.build
new file mode 100644
index 00000000..282a8da0
--- /dev/null
+++ b/meson.build
@@ -0,0 +1,331 @@
+project(
+ 'libmypaint',
+ 'c',
+ # API version: see https://github.com/mypaint/libmypaint/wiki/Versioning
+ # See http://semver.org/ for what this means.
+ version: '2.0.0-beta',
+ meson_version: '>=0.60.0',
+ default_options: [
+ 'c_std=c99',
+ ],
+)
+
+cc = meson.get_compiler('c')
+
+conf = configuration_data()
+
+pkgconfig = import('pkgconfig')
+pymod = import('python')
+
+prefix = get_option('prefix')
+includedir = prefix / get_option('includedir')
+localedir = prefix / get_option('localedir')
+
+###############################################################################
+# Project information.
+
+version_full = meson.project_version()
+version_dash_split = version_full.split('-')
+version_stable = version_dash_split[0]
+
+version_array = version_stable.split('.')
+version_major = version_array[0]
+version_minor = version_array[1]
+version_micro = version_array[2]
+
+# The API "platform" or "intercompatibility" version.
+#
+# This one is used for library name prefixes, for introspection
+# namespace versions, for gettext domains, and basically anything that
+# needs to change when backwards or forwards API compatibility changes.
+# Another way of thinking about it: it allows meaningful side by side
+# installations of libmypaint.
+api_platform_version = f'@version_major@.@version_minor@'
+api_name = f'libmypaint-@api_platform_version@'
+
+project_url = 'https://github.com/mypaint/libmypaint'
+
+conf.set('PACKAGE_NAME', meson.project_name())
+conf.set('PACKAGE_URL', project_url)
+conf.set('LIBMYPAINT_API_PLATFORM_VERSION', api_platform_version)
+conf.set('LIBMYPAINT_VERSION', version_stable)
+conf.set('LIBMYPAINT_VERSION_FULL', version_full)
+
+gettext_package = api_name
+conf.set_quoted(
+ 'GETTEXT_PACKAGE',
+ gettext_package,
+ description: 'The prefix for our gettext translation domains.',
+)
+
+###############################################################################
+# ABI version. Changes independently of API version.
+# See: https://autotools.io/libtool/version.html
+# https://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html
+# The rules are fiddly, and are summarized here.
+
+abi_current = 0 # inc when add/remove/change interfaces
+abi_revision = 0 # increment on every release
+abi_age = 0 # inc only if changes backward compat
+abi_soname_version = abi_current - abi_age
+abi_version_info = f'@abi_soname_version@.@abi_age@.@abi_revision@'
+
+###############################################################################
+# System detection, compiler options
+
+platform_win32 = (host_machine.system() == 'windows')
+platform_osx = (host_machine.system() == 'darwin')
+
+# Define strdup() in string.h under glibc >= 2.10 (POSIX.1-2008)
+add_project_arguments('-D_POSIX_C_SOURCE=200809L', language: 'c')
+
+###############################################################################
+# Dependencies
+
+libmath = cc.find_library('m', required: false)
+
+json = dependency('json-c')
+
+# glib
+gobject = dependency('gobject-2.0', required: get_option('glib'))
+use_glib = gobject.found()
+conf.set10('MYPAINT_CONFIG_USE_GLIB', use_glib)
+
+# GEGL
+gegl = dependency('gegl-0.4', 'gegl-0.3', required: get_option('gegl'))
+use_gegl = gegl.found()
+if use_gegl
+ gegl_gir = gegl.version().version_compare('>=0.4') ? 'Gegl-0.4' : 'Gegl-0.3'
+endif
+
+introspection_required_version = '1.32.0'
+introspection_feature = get_option(
+ 'introspection',
+).require(
+ use_glib,
+ error_message: 'Generating GObject introspection requires building with GLib support',
+)
+# For g-ir-scanner and g-ir-compiler as used by the gnome module.
+gi = dependency(
+ 'gobject-introspection-1.0',
+ version: f'>=@introspection_required_version@',
+ required: introspection_feature,
+ native: true,
+)
+use_introspection = gi.found()
+
+
+# OpenMP
+openmp = dependency('openmp', required: get_option('openmp'))
+
+## gperftools ##
+libprofiler = dependency('libprofiler', required: get_option('gperftools'))
+
+# Profiling
+if get_option('profiling')
+ add_project_arguments('-pg', language: 'c')
+endif
+
+# Internationalization
+cp = find_program('cp', required: false)
+msgfmt = find_program('msgfmt', required: false)
+msginit = find_program('msginit', required: false)
+msgmerge = find_program('msgmerge', required: false)
+mv = find_program('mv', required: false)
+xgettext = find_program('xgettext', required: false)
+fix_po_location = find_program('build-aux/fix-po-location.py')
+
+i18n_feature = get_option(
+ 'i18n',
+).require(
+ msgfmt.found(),
+ error_message: 'I18n support requires msgfmt from gettext to build mo files',
+).require(
+ mv.found(),
+ error_message: 'I18n support requires mv to install mo files to proper location',
+)
+libintl = dependency('intl', required: i18n_feature)
+use_i18n = libintl.found()
+conf.set10('HAVE_GETTEXT', use_i18n)
+
+
+# Docs
+enable_docs = get_option('docs')
+if enable_docs
+ doxygen = find_program('doxygen')
+ sphinx_build = find_program(
+ 'sphinx-build3',
+ 'sphinx-build-3',
+ 'sphinx-build2',
+ 'sphinx-build-2',
+ 'sphinx-build',
+ )
+
+ python3 = pymod.find_installation(
+ 'python3',
+ modules: [
+ 'breathe',
+ ],
+ required: true,
+ )
+
+ # todo: the python 'breathe' extension is also a dependency to doc building.
+ # the configure script should check for its existence.
+endif
+
+
+###############################################################################
+# Configure files
+
+toplevel_inc = include_directories('.')
+
+configure_file(
+ output: 'config.h',
+ configuration: conf,
+)
+
+brush_settings_headers = custom_target(
+ 'brush_settings_headers',
+ input: 'brushsettings.json',
+ output: [
+ 'mypaint-brush-settings-gen.h',
+ 'brushsettings-gen.h',
+ ],
+ command: [
+ find_program('python3'),
+ meson.current_source_dir() / 'generate.py',
+ '@OUTPUT@',
+ ],
+ depend_files: [
+ 'generate.py',
+ ],
+ install: true,
+ install_dir: [
+ includedir / api_name,
+ false,
+ ],
+)
+
+
+###############################################################################
+# Source files
+
+libmypaint_sources = [
+ 'brushmodes.c',
+ 'fifo.c',
+ 'helpers.c',
+ 'mypaint-brush-settings.c',
+ 'mypaint-brush.c',
+ 'mypaint-fixed-tiled-surface.c',
+ 'mypaint-mapping.c',
+ 'mypaint-matrix.c',
+ 'mypaint-rectangle.c',
+ 'mypaint-surface.c',
+ 'mypaint-symmetry.c',
+ 'mypaint-tiled-surface.c',
+ 'mypaint.c',
+ 'operationqueue.c',
+ 'rng-double.c',
+ 'tilemap.c',
+]
+
+libmypaint_introspectable_headers = [
+ 'mypaint-brush.h',
+ 'mypaint-brush-settings.h',
+ 'mypaint-fixed-tiled-surface.h',
+ 'mypaint-matrix.h',
+ 'mypaint-rectangle.h',
+ 'mypaint-surface.h',
+ 'mypaint-symmetry.h',
+ 'mypaint-tiled-surface.h',
+]
+
+libmypaint_public_headers = [
+ 'mypaint-config.h',
+ 'mypaint-glib-compat.h',
+ 'mypaint-mapping.h',
+ libmypaint_introspectable_headers,
+]
+
+install_headers(
+ libmypaint_public_headers,
+ subdir: api_name,
+)
+
+# Install in subdirectory
+if use_glib
+ install_headers(
+ 'glib/mypaint-brush.h',
+ subdir: api_name / 'glib',
+ )
+ libmypaint_introspectable_headers += 'glib/mypaint-brush.h'
+ libmypaint_public_headers += 'glib/mypaint-brush.h'
+endif
+
+# Do this after because you can't install_headers on a custom_target.
+libmypaint_introspectable_headers += brush_settings_headers[0]
+
+
+libmypaint = library(
+ f'mypaint-@api_platform_version@',
+ libmypaint_sources,
+ brush_settings_headers,
+ dependencies: [
+ gobject,
+ json,
+ libintl,
+ libmath,
+ openmp,
+ ],
+ version: abi_version_info,
+ install: true,
+)
+
+if use_introspection
+ gnome = import('gnome')
+
+ libmypaint_gir = gnome.generate_gir(
+ libmypaint,
+ nsversion: api_platform_version,
+ namespace: 'MyPaint',
+
+ sources: libmypaint_sources + libmypaint_introspectable_headers,
+ symbol_prefix: 'mypaint_',
+ identifier_prefix: 'MyPaint',
+
+ includes: [
+ 'GLib-2.0',
+ 'GObject-2.0',
+ ],
+ install: true,
+ )
+endif
+
+
+pkgconfig.generate(
+ libmypaint,
+ name: meson.project_name() + '-' + api_platform_version,
+ version: version_full,
+ description: 'MyPaint\'s brushstroke rendering library',
+ requires: [
+ json,
+ gobject,
+ ],
+ url: project_url,
+ subdirs: api_name,
+)
+
+
+if use_gegl
+ subdir('gegl')
+endif
+
+if use_i18n
+ subdir('po')
+endif
+
+subdir('tests')
+
+if enable_docs
+ subdir('doc')
+endif
diff --git a/meson_options.txt b/meson_options.txt
new file mode 100644
index 00000000..394187a9
--- /dev/null
+++ b/meson_options.txt
@@ -0,0 +1,50 @@
+option(
+ 'glib',
+ type: 'feature',
+ value: 'auto',
+ description: 'Use GLib',
+)
+option(
+ 'gegl',
+ type: 'feature',
+ value: 'auto',
+ description: 'Enable GEGL based code',
+)
+option(
+ 'openmp',
+ type: 'feature',
+ value: 'auto',
+ description: 'Compile with OpenMP',
+)
+option(
+ 'gperftools',
+ type: 'boolean',
+ value: false,
+ description: 'Enable gperftools in build, for profiling',
+)
+
+option(
+ 'profiling',
+ type: 'boolean',
+ value: false,
+ description: 'Turn on profiling',
+)
+
+option(
+ 'docs',
+ type: 'boolean',
+ value: false,
+ description: 'Enable documentation build',
+)
+option(
+ 'i18n',
+ type: 'feature',
+ value: 'auto',
+ description: 'Enable internationalization',
+)
+option(
+ 'introspection',
+ type: 'feature',
+ value: 'auto',
+ description: 'Enable GObject Instrospection (requires glib feature)',
+)
diff --git a/po/README.md b/po/README.md
index aca2d611..66366acd 100644
--- a/po/README.md
+++ b/po/README.md
@@ -16,7 +16,7 @@ We use [GNU gettext][gettext] for runtime translation of program text.
## After updating program strings
After changing any string in the source text which makes use of the
-gettext macros, you will need to run `./po/update_translation.sh`
+gettext macros, you will need to run `meson compile -C _build update-po`
and then commit the modified `po/libmypaint.pot` and `po/*.po`
files along with your changes.
Keeping this generated template file in the distribution
@@ -26,7 +26,7 @@ without having to ask us.
if all you want to do is compare diffs,
the `.pot` file alone can be updated by running:
```
-./po/update_translations.sh --only-template
+meson compile -C _build libmypaint.pot
```
# Information for translators
@@ -63,18 +63,16 @@ Before working on a translation,
update the `.po` file for your language.
For example, for the French translation, run:
- ./po/update_translations.sh fr
+ meson compile -C _build update-po-fr
## Use/Test the translation
After modifying the translation,
you need to rebuild and reinstall libmypaint to see the effect
in MyPaint, or in other apps that use `libmypaint`.
-If you have already built it, and `make install` does what you want,
-you can just use:
+If you have already built it, you can just use:
- make
- make install
+ meson install -C _build
To run MyPaint with a specific translation on Linux,
you can use the LANG environment variable
diff --git a/po/meson.build b/po/meson.build
new file mode 100644
index 00000000..737f6055
--- /dev/null
+++ b/po/meson.build
@@ -0,0 +1,160 @@
+# Translations need to be extracted from a generated file. Unfortunately,
+# there is currently no way to express the dependency with `i18n.gettext()`,
+# so one would need to explicitly rebuild the project before any action.
+# https://github.com/mesonbuild/meson/issues/1733
+# Additionally, we need to do some post-processing because xgettext
+# would use the generated file path for location info.
+#
+# Let’s recreate the built-in targets created by `i18n.gettext()` ourselves.
+
+fs = import('fs')
+
+languages = fs.read('LINGUAS').split()
+
+gettext_maintainer_tools = {
+ 'cp': cp.found(),
+ 'msginit': msginit.found(),
+ 'msgmerge': msgmerge.found(),
+ 'xgettext': xgettext.found(),
+}
+
+has_maintainer_tools = true
+foreach tool, available : gettext_maintainer_tools
+ if not available
+ has_maintainer_tools = false
+ warning(tool + ' not found, maintainer targets will not work')
+ endif
+endforeach
+
+# Generate compiled message catalogues (.mo files).
+foreach lang : languages
+ message_dir = localedir / lang / 'LC_MESSAGES'
+ mo_lang_name = f'@gettext_package@-@lang@.mo'
+
+ custom_target(
+ mo_lang_name,
+ input: f'@lang@.po',
+ # This should really be `@gettext_package@.mo` for every language,
+ # and the languages should be distinguished by directory name
+ # but Meson does not support output in a subdirectory.
+ # https://github.com/mesonbuild/meson/issues/2320
+ output: mo_lang_name,
+ command: [
+ msgfmt,
+ '--output-file', '@OUTPUT@',
+ '@INPUT@',
+ ],
+ install: true,
+ install_dir: message_dir,
+ install_tag: 'i18n',
+ )
+
+ # Since we have to use different names to disambiguate and there is no
+ # `rename` kwarg, we need to rename it after installation.
+ # This will break uninstall script but 🤷♀️
+ meson.add_install_script(
+ mv,
+ message_dir / mo_lang_name,
+ message_dir / f'@gettext_package@.mo',
+ )
+endforeach
+
+if has_maintainer_tools
+ update_po_targets = []
+
+ # Update PO template.
+
+ pot = custom_target(
+ '_update-pot',
+ input: brush_settings_headers[1],
+ depends: [
+ brush_settings_headers,
+ ],
+ command: [
+ xgettext,
+ '--package-name=' + gettext_package,
+ '--output-dir=' + meson.project_build_root(),
+ '--directory=' + meson.project_build_root(),
+ '--output=@OUTPUT@',
+ '--add-comments',
+ '--keyword=N_:1',
+ '@INPUT@',
+ # The input is a generated file and our generator includes
+ # the actual source location in a comment for translators.
+ # We are going to convert it into a proper location comment
+ # in the next step.
+ '--no-location',
+ ],
+ output: 'libmypaint.pot.in',
+ )
+
+ pot = custom_target(
+ '_fix-pot',
+ input: pot,
+ command: [
+ # ...transform special generated comments into accurate source locations.
+ fix_po_location,
+ '@INPUT@',
+ '@OUTPUT@',
+ ],
+ # Keep old file name for backwards compatibility.
+ output: 'libmypaint.pot',
+ )
+
+ update_pot = run_target(
+ 'libmypaint.pot',
+ command: [
+ cp,
+ pot,
+ meson.current_source_dir(),
+ ],
+ depends: pot,
+ )
+
+ # Update PO files.
+
+ foreach lang : languages
+ pofile = meson.current_source_dir() / f'@lang@.po'
+ update_po_target = f'update-po-@lang@'
+
+ if fs.is_file(pofile)
+ update_po_targets += run_target(
+ update_po_target,
+ command: [
+ msgmerge,
+ '--quiet',
+ '--output-file', pofile,
+ pofile,
+ # `run_target` cannot depend on another `run_target`.
+ # Use .pot file from build directory.
+ pot,
+ ],
+ depends: [
+ pot,
+ ],
+ )
+ else
+ update_po_targets += run_target(
+ update_po_target,
+ command: [
+ msginit,
+ # `run_target` cannot depend on another `run_target`.
+ # Use .pot file from build directory.
+ '--input=' + pot,
+ '--output-file=' + pofile,
+ '--locale=' + lang,
+ '--no-translator',
+ ],
+ depends: [
+ pot,
+ ],
+ )
+ endif
+ endforeach
+
+ alias_target(
+ f'update-po',
+ update_pot,
+ update_po_targets,
+ )
+endif
diff --git a/po/update_translations.sh b/po/update_translations.sh
deleted file mode 100755
index 1d0b919b..00000000
--- a/po/update_translations.sh
+++ /dev/null
@@ -1,142 +0,0 @@
-#!/usr/bin/env bash
-
-update_translations()
-{
- cd $(dirname "$1")
- local HELP
- HELP="\
-====
-Update translation files: generated sources / po template / po language files
-Usage: $1 [--force] [[--only-template] | [LANG...]]
-
-If no languages are specified, and --only-template is not set, all .po files in
-the directory will be updated, same as running: $1 *.po
-====
-"
- shift
-
- sec() { date -r "$1" "+%s"; }
- err() { >&2 echo -e "\e[91m""Error: $@""\e[0m"; }
- print_errors()
- {
- for e in "$@"
- do
- err "$e"
- done
- }
-
- local ORIG GEN_SRC ENUM GEN TEMPLATE FORCE ONLY_TEMPLATE
- ORIG=../brushsettings.json
- GEN_SRC=../brushsettings-gen.h
- ENUM=../mypaint-brush-settings-gen.h
- GEN_SCRIPT=../generate.py
- TEMPLATE=libmypaint.pot
-
- local langs errors
- langs=()
- errors=()
-
- while [ -n "$1" ]
- do
- case "$1" in
- --help)
- echo "$HELP" && exit 0
- ;;
- --force)
- FORCE=1
- ;;
- --only-template)
- ONLY_TEMPLATE=1
- ;;
- -*)
- errors+=("Unrecognized option: $1")
- ;;
- *)
- local f
- f="${1%%.po}.po"
- if [ ! -e "$f" ]
- then
- errors+=("Not found: $f - LANG must be the code or .po file for an existing language")
- else
- langs+=("$f")
- fi
- ;;
- esac
- shift
- done
-
- # Sanity check
- if [ -n "$ONLY_TEMPLATE" -a -n "$langs" ]
- then
- errors+=("Don't specify languages when using ``--only-template``")
- fi
- # Print usage instructions followed by error message(s)
- [ -n "$errors" ] && >&2 echo "$HELP" && print_errors "${errors[@]}" && exit 1
-
- # Check if the message source file needs to be (re)generated.
- # ( generated source: not present,older than basis, older than script )
- if [ -n "$FORCE" -o ! -e "$GEN_SRC" -o \
- $(sec "$GEN_SRC") -lt $(sec "$ORIG") -o \
- $(sec "$GEN_SRC") -lt $(sec "$GEN_SCRIPT") ]
- then
- [ -z "$FORCE" ] &&
- echo "Generated file missing or out of date, generating..." ||
- echo "Generating (forced)..."
- python "$GEN_SCRIPT" "$ENUM" "$GEN_SRC" ||
- (echo "Failed to generate source file!" && exit 1)
- fi
-
- # Check if the template file appears up to date
- if [ -z "$FORCE" -a -e "$TEMPLATE" -a $(sec "$GEN_SRC") -lt $(sec "$TEMPLATE") ]
- then
- echo "$TEMPLATE up to date, skipping extraction (use --force to override)."
- else
- local temp_template temp_diff
- temp_template=$(mktemp)
- temp_diff=$(mktemp)
- # Omit locations from the generated file, and instead...
- xgettext --no-location -c -kN_:1 -o - "$GEN_SRC" |
- # ...transform special generated comments into accurate source locations.
- sed -E "s@^#\. (: $ORIG:.*)@#\1@" > "$temp_template"
- # Don't update template if the only change is the creation date
- diff --suppress-common-lines -y "$TEMPLATE" "$temp_template" > "$temp_diff"
- if [ $(wc -l < "$temp_diff") -eq 1 -a
- $(grep -i -o "POT-Creation-Date" "$temp_diff" | wc -l) -eq 2 ]
- then
- echo "$TEMPLATE unchanged"
- else
- mv "$temp_template" "$TEMPLATE" && echo "$TEMPLATE updated."
- fi
- fi
-
- # If requested, don't update any languages
- [ -n "$ONLY_TEMPLATE" ] && exit 0
-
- # If no languages are specified, try to update all of them
- if [ -z "${langs[*]}" ]
- then
- langs=($(ls *.po))
- fi
-
- local failed_updates
- failed_updates=()
-
- # Update the language files based on the template
- for lang in "${langs[@]}"
- do
- msgmerge -q -U "$lang" "$TEMPLATE" || failed_updates+=("$lang")
- done
-
- echo "Successfully processed $((${#langs[@]} - ${#failed_updates[@]})) language files."
- if [ -n "${failed_updates[*]}" ]
- then
- err "Failed to update ${#failed_updates[@]} language files:"
- for f in "${failed_updates[@]}"
- do
- err "$f"
- done
- exit 1
- fi
-}
-
-update_translations "$0" "$@"
diff --git a/tests/meson.build b/tests/meson.build
new file mode 100644
index 00000000..c9cba80e
--- /dev/null
+++ b/tests/meson.build
@@ -0,0 +1,92 @@
+tests = [
+ {
+ 'name': 'test-brush-load',
+ 'timeout': 5,
+ },
+ {
+ 'name': 'test-brush-persistence',
+ 'timeout': 5,
+ },
+ {
+ 'name': 'test-details',
+ 'timeout': 60,
+ },
+ {
+ 'name': 'test-fixed-tiled-surface',
+ 'timeout': 1000,
+ },
+ {
+ 'name': 'test-rng',
+ 'timeout': 5,
+ },
+]
+
+if use_gegl
+ tests += {
+ 'name': 'test-gegl-surface',
+ 'srcs': 'gegl/test-gegl-surface.c',
+ 'deps': [
+ gegl,
+ ],
+ 'incs': [
+ libmypaint_gegl_inc,
+ ],
+ 'link': [
+ libmypaint_gegl,
+ ],
+ 'timeout': 2000,
+ }
+endif
+
+libmypaint_tests_lib = static_library(
+ 'mypaint-tests',
+ 'mypaint-benchmark.c',
+ 'mypaint-test-surface.c',
+ 'mypaint-utils-stroke-player.c',
+ 'testutils.c',
+ brush_settings_headers,
+ c_args: [
+ '-DLIBMYPAINT_TESTING_ABS_TOP_SRCDIR="@0@"'.format(meson.project_source_root()),
+ ],
+ include_directories: toplevel_inc,
+ dependencies: [
+ gobject,
+ ],
+)
+
+foreach test : tests
+ test_name = test.get('name')
+ test_srcs = test.get('srcs', test_name + '.c')
+ test_deps = test.get('deps', [])
+ test_incs = test.get('incs', [])
+ test_link = test.get('link', [])
+ test_timeout = test.get('timeout', 30)
+
+ test_exe = executable(
+ test_name,
+ test_srcs,
+ brush_settings_headers,
+ c_args: '-DLIBMYPAINT_TESTING_ABS_TOP_SRCDIR="@0@"'.format(meson.project_source_root()),
+ include_directories: [
+ toplevel_inc,
+ test_incs,
+ ],
+ link_with: [
+ libmypaint,
+ libmypaint_tests_lib,
+ test_link,
+ ],
+ dependencies: [
+ gobject,
+ libmath,
+ libprofiler,
+ test_deps,
+ ],
+ )
+
+ test(
+ test_name,
+ test_exe,
+ timeout: test_timeout,
+ )
+endforeach