diff --git a/.travis.yml b/.travis.yml index 1c84b87a..369c4f5d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,7 @@ compiler: - gcc sudo: required +services: docker dist: trusty @@ -15,18 +16,36 @@ addons: - libgirepository1.0-dev - libgegl-dev -script: - - ./autogen.sh - - ./configure - - make - - make clean - - make distcheck - - make maintainer-clean - - ./autogen.sh - - ./configure --with-introspection - - make - - make maintainer-clean - - ./autogen.sh - - ./configure --with-gegl - - make - - make maintainer-clean +jobs: + include: + - stage: "Build" + name: "Autotools build" + script: + - ./autogen.sh + - ./configure + - make + - make clean + - make distcheck + - make maintainer-clean + - ./autogen.sh + - ./configure --with-introspection + - make + - make maintainer-clean + - ./autogen.sh + - ./configure --with-gegl + - make + - make maintainer-clean + + - name: "Meson build" + script: + - sudo docker pull ubuntu:disco + - sudo docker run -t -v $(pwd):/sources ubuntu:disco /bin/bash -ec " + apt update -y + && apt install -y meson gettext libjson-c-dev libgirepository1.0-dev libgegl-dev git + && meson /sources _build --buildtype=release + && ninja -C _build test + && ninja -C _build dist + && meson /sources _build_gegl --buildtype=release -Dgegl=true + && ninja -C _build_gegl test + && ninja -C _build_gegl dist + " diff --git a/appveyor_build.sh b/appveyor_build.sh index 88c15a9b..fc282651 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" +# Meson build, we need a way to parallel this with Autotools +meson setup _build --buildtype=release -Dgegl=false -Ddocs=false +meson dist -C _build +rm -rf _build + ./autogen.sh ./configure make distcheck 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..59934796 --- /dev/null +++ b/gegl/meson.build @@ -0,0 +1,61 @@ +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( + 'mypaint-gegl-@0@'.format(api_platform_version), + libmypaint_gegl_sources, + include_directories: toplevel_inc, + link_with: libmypaint, + dependencies: [ + json, + gobject, + gegl, + ], + install: true, +) + +install_headers( + libmypaint_gegl_headers, + subdir: 'libmypaint-gegl', +) + + +if get_option('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', + url: project_url, + subdirs: 'libmypaint-gegl', +) diff --git a/meson.build b/meson.build new file mode 100644 index 00000000..a5b36e7f --- /dev/null +++ b/meson.build @@ -0,0 +1,294 @@ +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') + +############################################################################### +# Project informations + +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 = '@0@.@1@'.format(version_major, version_minor) +api_name = 'libmypaint-@0@'.format(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 +abi_revision = 0 # increment on every release +abi_current = 0 # inc when add/remove/change interfaces +abi_age = 0 # inc only if changes backward compat +# FIXME: Not correct. +abi_version_info = f'@abi_current@.@abi_revision@.@abi_age@' + +############################################################################### +# 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' + + +# 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 +have_i18n = false +if get_option('i18n') + if not cc.has_header('libintl.h') + error('libintl.h must be available for i18n support') + endif + if platform_win32 or platform_osx + libintl = cc.find_library('intl') + else + libintl = [] + endif + have_i18n = true +endif +conf.set10('HAVE_GETTEXT', have_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: [ + get_option('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-rectangle.h', + 'mypaint-surface.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( + 'mypaint-@0@'.format(api_platform_version), + libmypaint_sources, + brush_settings_headers, + dependencies: [ + gobject, + json, + libintl, + libmath, + openmp, + ], + version: abi_version_info, + install: true, +) + + +if get_option('introspection') + if not use_glib + error('Generating GObject introspection requires building with GLib support') + endif + 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', + url: project_url, + subdirs: api_name, +) + + +if use_gegl + subdir('gegl') +endif + +if get_option('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..b6121244 --- /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: 'boolean', + value: true, + description: 'Enable internationalization', +) +option( + 'introspection', + type: 'boolean', + value: true, + description: 'Enable GObject Instrospection', +) diff --git a/po/POTFILES.meson b/po/POTFILES.meson new file mode 100644 index 00000000..8f9a6e5d --- /dev/null +++ b/po/POTFILES.meson @@ -0,0 +1,7 @@ +# Files from libmypaint source which would have been marked for runtime +# translation of messages. + +# Dummy file to use instead of POTFILES.in since that one is not compatible +# with xgettext. It is empty since the files we care about are generated +# by the build system so we have to pass the paths explicitly when doing +# out-of-tree builds. diff --git a/po/meson.build b/po/meson.build new file mode 100644 index 00000000..d821d27b --- /dev/null +++ b/po/meson.build @@ -0,0 +1,23 @@ +i18n = import('i18n') + +i18n.gettext( + gettext_package, + args: [ + # Extract translations from generated file. + # Unfortunately, there is currently no way to express the dependency + # so one will need to build the project before updating the po template. + # https://github.com/mesonbuild/meson/issues/1733 + brush_settings_headers[1].full_path(), + # Avoid POTFILES.in since that uses intltool syntax incompatible with xgettext + # and lists files that do not exist in the source directory. + '--files-from=' + (meson.current_source_dir() / 'POTFILES.meson'), + # Keep old file name for backwards compatibility. + '--output=libmypaint.pot', + # Unlike intltool-extract, xgettext does not support overriding location + # with a comment in the generated file – it will preserve it as a regular + # comment. Let’s not include the location in the generated file + # since it is less useful. + '--no-location', + ], + preset: 'glib', +) diff --git a/tests/meson.build b/tests/meson.build new file mode 100644 index 00000000..2477145b --- /dev/null +++ b/tests/meson.build @@ -0,0 +1,85 @@ +tests = [ + { + 'name': 'test-brush-load', + }, + { + 'name': 'test-brush-persistence', + }, + { + 'name': 'test-details', + }, + { + 'name': 'test-fixed-tiled-surface', + }, + { + 'name': 'test-rng', + }, +] + +if use_gegl + tests += { + 'name': 'test-gegl-surface', + 'srcs': 'gegl/test-gegl-surface.c', + 'deps': [ + gegl, + ], + 'incs': [ + libmypaint_gegl_inc, + ], + 'link': [ + libmypaint_gegl, + ], + } +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_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: 500, + ) +endforeach