From bdde78f87277ed9d74bd1ba985db1201611d95a4 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Mon, 9 Apr 2018 00:23:15 -0400 Subject: [PATCH] Add Meson build system MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Jan Tojnar Co-authored-by: Félix Piédallu --- .github/workflows/test.yaml | 36 +++- appveyor_build.sh | 9 +- build-aux/fix-po-location.py | 21 +++ doc/meson.build | 37 ++++ doc/source/meson.build | 5 + gegl/meson.build | 66 +++++++ meson.build | 323 +++++++++++++++++++++++++++++++++++ meson_options.txt | 50 ++++++ po/meson.build | 159 +++++++++++++++++ tests/meson.build | 92 ++++++++++ 10 files changed, 795 insertions(+), 3 deletions(-) create mode 100755 build-aux/fix-po-location.py create mode 100644 doc/meson.build create mode 100644 doc/source/meson.build create mode 100644 gegl/meson.build create mode 100644 meson.build create mode 100644 meson_options.txt create mode 100644 po/meson.build create mode 100644 tests/meson.build 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/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/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/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..2bb2eb58 --- /dev/null +++ b/meson.build @@ -0,0 +1,323 @@ +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') + +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_prerelease = version_dash_split.get(1, '') # may be blank + +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.', +) + +############################################################################### +# Libtool versioning + +# ABI version see: https://autotools.io/libtool/version.html +# https://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html +# https://github.com/pvanhoof/dir-examples/blob/97a7ba2c9c54f35e8f3dfb0452f6ce321719c30a/meson-example/meson.build#L52-L54 +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', + ) + # 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/meson.build b/po/meson.build new file mode 100644 index 00000000..c29b5444 --- /dev/null +++ b/po/meson.build @@ -0,0 +1,159 @@ +# 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( + 'libmypaint-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( + 'libmypaint-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'@gettext_package@-@lang@-update-po' + + 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'@gettext_package@-update-po', + update_po_targets, + ) +endif 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