Porting poppler to meson

This article is part of the Don't shell out! series.

It took a hot minute.

Try several weeks.

Well, yeah. I got to contribute to a bunch of open-source projects in the meantime though, so I'm fairly pleased with it!

In the current state of things, I have: a fork of glib, a (tiny) fork of cairo, and a whole set of meson build files for poppler... and it's really easy to build for both Windows & Linux!

All of the following happens in poppler-meson, but you can assume we're pretty much starting from scratch: a lot of my fixes have already landed, and the others will soon, so for example I don't have to pass -DFFI_STATIC_BUILD=1 myself.

Let's get through it real quick:

In poppler-meson there's a top-level meson.build that reads like this:

meson.build file
project('poppler-meson', 'c', version: '1.0.0', meson_version: '>= 0.60.2', default_options: [ 'default_library=static', ], ) poppler_dep = dependency( 'poppler', static: true, )

Because not every fix has landed yet, we still want to override some dependencies that would normally be pulled by poppler.

subprojects/glib.wrap points to my fork of glib:

meson .wrap file
[wrap-git] directory = glib url=https://gitlab.gnome.org/fasterthanlime/glib revision=amos/win-msvc-static-linking [provide] dependency_names = gthread-2.0, gobject-2.0, gmodule-no-export-2.0, gmodule-export-2.0, gmodule-2.0, glib-2.0, gio-2.0, gio-win32-2.0, gio-unix-2.0 program_names = glib-genmarshal, glib-mkenums, glib-compile-schemas, glib-compile-resources, gio-querymodules, gdbus-codegen

And so does subprojects/cairo.wrap:

meson .wrap file
[wrap-git] url = https://gitlab.freedesktop.org/fasterthanlime/cairo.git revision = amos/win32-static-build

Can't make your mind about branch names huh?

Less judging more shipping.

Both these subprojects have their own dependencies (which also get checked out as subprojects, but which I don't track in git), but also, they do something like this:

meson.build file
if meson.version().version_compare('>=0.54.0') meson.override_dependency('glib-2.0', libglib_dep) endif

Which means that when poppler or cairo will request dependency glib-2.0, it'll use my fork, and not another version of glib.

subprojects/poppler.wrap is interesting: it fetches poppler sources from the official 21.12.0 release, and then overlays a folder on top of it, which contains only meson.build files:

meson .wrap file
[wrap-file] directory = poppler source_url = https://poppler.freedesktop.org/poppler-21.12.0.tar.xz source_filename = poppler-21.12.0.tar.xz source_hash = acb840c2c1ec07d07e53c57c4b3a1ff3e3ee2d888d44e1e9f2f01aaf16814de7 patch_directory = poppler-21.12.0 [provide] dependency_names = poppler, poppler-glib

That folder is in subprojects/packagefiles/poppler-21.12.0.

The first thing we should probably look at is meson_options.txt: it's just a list of features poppler can be built with & without:

meson.build file
option( 'glib', type: 'feature', value: 'enabled', description: 'Compile poppler with glib wrapper', ) option( 'cairo', type: 'feature', value: 'auto', description: 'Enable the cairo rendering backend', ) # (etc.)

I didn't make an effort to ensure all combinations build, I just went for the minimal build I needed, so it's not ready to contribute upstream yet.

For comparison, in CMake, these look like this:

cmake
option(ENABLE_GLIB "Compile poppler glib wrapper." ON)

The main meson.build file starts by defining a project and some variables we'll need later;

meson.build file
project('poppler', 'cpp', version: '21.12.0', meson_version: '>= 0.60.2', default_options: ['default_library=static'], ) gnome = import('gnome') glib_required = '1.10.0' poppler_version = meson.project_version() poppler_version_array = poppler_version.split('.') poppler_major_version = poppler_version_array[0].to_int() poppler_minor_version = poppler_version_array[1].to_int() poppler_micro_version = poppler_version_array[2].to_int() os_unix = false os_linux = false os_win32 = false os_darwin = false # Some windowing system backends depend on the platform we're # building for, so we need to ensure they are disabled; in other # cases, they are the only windowing system available, so we need # to ensure they are enabled if host_machine.system() == 'darwin' os_darwin = true elif host_machine.system() == 'windows' os_win32 = true elif host_machine.system() == 'linux' os_linux = true endif os_unix = not os_win32 poppler_prefix = get_option('prefix') poppler_includedir = join_paths(poppler_prefix, get_option('includedir')) poppler_libdir = join_paths(poppler_prefix, get_option('libdir')) poppler_pkgconfigdir = join_paths(poppler_libdir, 'pkgconfig') cc = meson.get_compiler('cpp')

Then retrieving all the options we've defined, and finding dependencies:

meson.build file
jpeg_opt = get_option('jpeg') openjpeg_opt = get_option('openjpeg') libcurl_opt = get_option('libcurl') zlib_opt = get_option('zlib') zlib_uncompress_opt = get_option('zlib-uncompress') zlib_dep = dependency('zlib', required: zlib_opt) glib_opt = get_option('glib') glib_dep = dependency('glib-2.0', version: '>= ' + glib_required, static: true, required: glib_opt, default_options: ['tests=false'], fallback: 'glib', ) gobject_dep = dependency('gobject-2.0', version: '>= ' + glib_required, required: glib_opt, ) gio_dep = dependency('gio-2.0', version: '>= ' + glib_required, required: glib_opt, ) cairo_opt = get_option('cairo') cairo_dep = dependency('cairo', static: true, required: cairo_opt, default_options: [ 'fontconfig=enabled', 'freetype=enabled', 'cogl=disabled', 'gl-backend=disabled', 'glesv2=disabled', 'glesv3=disabled', 'drm=disabled', 'openvg=disabled', 'png=enabled', 'quartz=disabled', 'qt=disabled', 'tee=disabled', 'xcb=disabled', 'xlib=disabled', 'xlib-xcb=disabled', 'zlib=enabled', 'tests=disabled', 'glib=enabled', ], fallback: ['cairo'], ) cairogobj_dep = dependency('cairo-gobject', required: cairo_opt, fallback: ['cairo'], ) nss3_opt = get_option('nss3') freetype_dep = dependency('freetype2', required: true, fallback: ['freetype2', 'freetype_dep'], )

You can see it's.. opinionated already, disabling most of cairo's features.

Then, we have a bunch of checks! To make sure we have a certain header, or that a type is defined in a certain header, etc. Just portability stuff:

meson.build file
cdata = configuration_data() # Header checks cdata.set('HAVE_DLFCN_H', cc.has_header('dlfcn.h')) cdata.set('HAVE_FCNTL_H', cc.has_header('fcntl.h')) cdata.set('HAVE_STDLIB_H', cc.has_header('stdlib.h')) cdata.set('HAVE_SYS_MMAN_H', cc.has_header('sys/mman.h')) cdata.set('HAVE_SYS_STAT_H', cc.has_header('sys/stat.h')) cdata.set('HAVE_UNISTD_H', cc.has_header('unistd.h')) cdata.set('HAVE_FSEEK64', cc.has_function('fseek64')) cdata.set('HAVE_FSEEKO', cc.has_header_symbol('stdio.h', 'fseeko')) cdata.set('HAVE_FTELL64', cc.has_function('ftell64')) cdata.set('HAVE_PREAD64', cc.has_function('pread64')) cdata.set('HAVE_LSEEK64', cc.has_function('lseek64')) cdata.set('HAVE_GMTIME_R', cc.has_function('gmtime_r')) cdata.set('HAVE_GETTIMEOFDAY', cc.has_function('gettimeofday')) cdata.set('HAVE_LOCALTIME_R', cc.has_function('localtime_r')) cdata.set('HAVE_POPEN', cc.has_function('popen')) cdata.set('HAVE_MKSTEMP', cc.has_function('mkstemp')) cdata.set('HAVE_STRTOK_R', cc.has_function('strtok_r')) dir_code = ''' int main(int argc, char *argv[]) { DIR* d = 0; return 0; } ''' cdata.set('HAVE_DIRENT_H', cc.compiles('#include <dirent.h>\n' + dir_code, name : 'Header <dirent.h> defines DIR')) cdata.set('HAVE_NDIR_H', cc.compiles('#include <ndir.h>\n' + dir_code, name : 'Header <ndir.h> defines DIR')) cdata.set('HAVE_SYS_DIR_H', cc.compiles('#include <sys/dir.h>\n' + dir_code, name : 'Header <sys/dir.h> defines DIR')) cdata.set('HAVE_SYS_NDIR_H', cc.compiles('#include <sys/ndir.h>\n' + dir_code, name : 'Header <sys/ndir.h> defines DIR')) cdata.set('HAVE_NANOSLEEP', cc.has_function('nanosleep')) # Enable these unconditionally. cdata.set('OPI_SUPPORT', '1') cdata.set('TEXTOUT_WORD_LIST', '1') # Stubs cdata.set('ICONV_CONST', '') cdata.set('POPPLER_DATADIR', '') cdata.set('POPPLER_VERSION', poppler_version) if libcurl_opt.enabled() cdata.set('POPPLER_HAS_CURL_SUPPORT', '1') endif

That configuration data passed later to "configure_file" to generate a poppler-config.h, in other meson.build files (we'll get to that).

pkg-config files (.pc) have other variables, which we also need to set: it's mostly paths.

meson.build file
pkgconfig_cdata = configuration_data() pkgconfig_cdata.set('CMAKE_INSTALL_PREFIX', poppler_prefix) pkgconfig_cdata.set('CMAKE_INSTALL_FULL_LIBDIR', poppler_libdir) pkgconfig_cdata.set('CMAKE_INSTALL_FULL_INCLUDEDIR', poppler_includedir) pkgconfig_cdata.set('POPPLER_VERSION', poppler_version) pkgconfig_cdata.set('GLIB_REQUIRED', glib_required) pkgconfig_cdata.set('CAIRO_VERSION', cairo_dep.version()) # Configure "Requires" field & install .pc files for packagers pkgconfig_cdata.set('PC_REQUIRES', '') pkgconfig_cdata.set('PC_REQUIRES_PRIVATE', 'Requires.private: poppler = @0@'.format(poppler_version))

Then, there's an internal/ subdirectory, which is not in the original poppler sources:

meson.build file
# put config.h in an internal directory so # users don't include it by accident subdir('internal') conf_inc = include_directories('internal')

We'll get to what's inside that internal/ subdirectory later.

There's two main poppler source directories we're interested in: poppler/, and glib/. There's also some base utilities, like goo/ and fofi/, but those are actually handled by poppler/meson.build.

meson.build file
subdir('poppler') if glib_opt.enabled() subdir('glib') endif

Finally, we print a nice summary of everything:

meson.build file
#### Summary #### summary('zlib support', zlib_dep.found(), section: 'Features') summary('glib support', glib_dep.found(), section: 'Features') summary('cairo support', cairo_dep.found(), section: 'Features') summary('Compiler', cc.get_id(), section: 'Toolchain') summary('Linker', cc.get_linker_id(), section: 'Toolchain') # Build summary('Debugging', get_option('debug'), section: 'Build') summary('Optimization', get_option('optimization'), section: 'Build') # Directories summary('prefix', poppler_prefix, section: 'Directories') summary('includedir', poppler_includedir, section: 'Directories') summary('libdir', poppler_libdir, section: 'Directories')

Here's what the summary looks like on Fedora 35, for example:

Shell session
poppler 21.12.0 Features zlib support : True glib support : True cairo support: True Toolchain Compiler : gcc Linker : ld.bfd Build Debugging : False Optimization : 3 Directories prefix : /tmp/poppler-prefix includedir : /tmp/poppler-prefix/include libdir : /tmp/poppler-prefix/lib64

And here's what it looks like on Windows:

PowerShell session
poppler 21.12.0 Features zlib support : True glib support : True cairo support: True Toolchain Compiler : msvc Linker : link Build Debugging : True Optimization : 0 Directories prefix : C:\poppler-prefix includedir : C:/poppler-prefix/include libdir : C:/poppler-prefix/lib

Wowee, everybody gets a different slash!

Well, as it turns out, Windows has accepted forward-slash as an alternate path separator for a long time.

Oh word?

Yeah! I've tried finding a definitive source on this, but I mostly found endless arguments

So for now, just trust me that they both work:

PowerShell session
(Get-Item C:\Users\amos).Name && (Get-Item C:/Users/amos).Name amos amos

That's it for the top-level meson.build file!

Now let's peek inside internal/meson.build:

meson.build file
configure_file( input: '../config.h.cmake', output: 'config.h', configuration: cdata, format: 'cmake', )

That's it! config.h.cmake ships with poppler, and it looks like this:

C code
/* config.h. Generated from config.h.cmake by cmake. */ /* Build against libcurl. */ #cmakedefine ENABLE_LIBCURL 1 /* (cut.) */ /* Define to 1 if you have the <dirent.h> header file, and it defines `DIR'. */ #cmakedefine HAVE_DIRENT_H 1 /* Define to 1 if you have the <dlfcn.h> header file. */ #cmakedefine HAVE_DLFCN_H 1 /* Define to 1 if you have the <fcntl.h> header file. */ #cmakedefine HAVE_FCNTL_H 1 /* (cut.) */

Once configured, it ends up in build/subprojects/poppler-21.12.0/internal/config.h, looking like so:

C code
/* config.h. Generated from config.h.cmake by cmake. */ /* Build against libcurl. */ /* #undef ENABLE_LIBCURL */ /* (cut.) */ /* Define to 1 if you have the <dirent.h> header file, and it defines `DIR'. */ #define HAVE_DIRENT_H /* Define to 1 if you have the <dlfcn.h> header file. */ #define HAVE_DLFCN_H /* Define to 1 if you have the <fcntl.h> header file. */ #define HAVE_FCNTL_H /* (cut.) */

As you can see, the Meson folks have made it rather easy to "port from CMake", by supporting the various formats use for configuring files. We'll see one more example of this.

Time to look at poppler/meson.build

yawn are we almost done?

Patience bear (and others) - the result is worth all that work.

Mh. If you say so.

First, we configure poppler-config.h.cmake, another magic header file:

meson.build file
configure_file( input: 'poppler-config.h.cmake', output: 'poppler-config.h', configuration: cdata, format: 'cmake', )

That one contains stuff like version numbers, and some duplicates from config.h. For example, this:

C code
/* Defines the poppler version. */ #ifndef POPPLER_VERSION #define POPPLER_VERSION "${POPPLER_VERSION}" #endif

Turns into this:

C code
/* Defines the poppler version. */ #ifndef POPPLER_VERSION #define POPPLER_VERSION "21.12.0" #endif

Next, we list all source files we're gonna need. This one gets pretty long...

Ready? Set? Scroll!

meson.build file
poppler_sources = files([ '../goo/gbase64.cc', '../goo/gbasename.cc', '../goo/gfile.cc', '../goo/GooTimer.cc', '../goo/GooString.cc', '../goo/NetPBMWriter.cc', '../goo/PNGWriter.cc', '../goo/TiffWriter.cc', '../goo/JpegWriter.cc', '../goo/ImgWriter.cc', '../goo/gstrtod.cc', '../goo/grandom.cc', '../goo/glibc.cc', '../goo/glibc_strtok_r.cc', '../fofi/FoFiBase.cc', '../fofi/FoFiEncodings.cc', '../fofi/FoFiTrueType.cc', '../fofi/FoFiType1.cc', '../fofi/FoFiType1C.cc', '../fofi/FoFiIdentifier.cc', 'Annot.cc', 'AnnotStampImageHelper.cc', 'Array.cc', 'BBoxOutputDev.cc', 'CachedFile.cc', 'Catalog.cc', 'CertificateInfo.cc', 'CharCodeToUnicode.cc', 'CMap.cc', 'DateInfo.cc', 'Decrypt.cc', 'Dict.cc', 'Error.cc', 'FDPDFDocBuilder.cc', 'FILECacheLoader.cc', 'FileSpec.cc', 'FontEncodingTables.cc', 'FontInfo.cc', 'Form.cc', 'Function.cc', 'Gfx.cc', 'GfxFont.cc', 'GfxState.cc', 'GlobalParams.cc', 'Hints.cc', 'ImageEmbeddingUtils.cc', 'JArithmeticDecoder.cc', 'JBIG2Stream.cc', 'JSInfo.cc', 'Lexer.cc', 'Linearization.cc', 'Link.cc', 'LocalPDFDocBuilder.cc', 'MarkedContentOutputDev.cc', 'Movie.cc', 'NameToCharCode.cc', 'Object.cc', 'OptionalContent.cc', 'Outline.cc', 'OutputDev.cc', 'Page.cc', 'PageLabelInfo.cc', 'PageTransition.cc', 'Parser.cc', 'PDFDoc.cc', 'PDFDocBuilder.cc', 'PDFDocEncoding.cc', 'PDFDocFactory.cc', 'PreScanOutputDev.cc', 'ProfileData.cc', 'PSOutputDev.cc', 'PSTokenizer.cc', 'Rendition.cc', 'SecurityHandler.cc', 'SignatureInfo.cc', 'Sound.cc', 'SplashOutputDev.cc', 'Stream.cc', 'StructElement.cc', 'StructTreeRoot.cc', 'TextOutputDev.cc', 'UnicodeMap.cc', 'UnicodeMapFuncs.cc', 'UnicodeTypeTable.cc', 'UTF.cc', 'ViewerPreferences.cc', 'XRef.cc', '../splash/Splash.cc', '../splash/SplashBitmap.cc', '../splash/SplashClip.cc', '../splash/SplashFTFont.cc', '../splash/SplashFTFontEngine.cc', '../splash/SplashFTFontFile.cc', '../splash/SplashFont.cc', '../splash/SplashFontEngine.cc', '../splash/SplashFontFile.cc', '../splash/SplashFontFileID.cc', '../splash/SplashPath.cc', '../splash/SplashPattern.cc', '../splash/SplashScreen.cc', '../splash/SplashState.cc', '../splash/SplashXPath.cc', '../splash/SplashXPathScanner.cc', 'CourierWidths.pregenerated.c', 'CourierBoldWidths.pregenerated.c', 'CourierBoldObliqueWidths.pregenerated.c', 'CourierObliqueWidths.pregenerated.c', 'HelveticaWidths.pregenerated.c', 'HelveticaBoldWidths.pregenerated.c', 'HelveticaBoldObliqueWidths.pregenerated.c', 'HelveticaObliqueWidths.pregenerated.c', 'SymbolWidths.pregenerated.c', 'TimesBoldWidths.pregenerated.c', 'TimesBoldItalicWidths.pregenerated.c', 'TimesItalicWidths.pregenerated.c', 'TimesRomanWidths.pregenerated.c', 'ZapfDingbatsWidths.pregenerated.c', ]) if libcurl_opt.enabled() poppler_sources += files([ 'CurlCachedFile.cc', 'CurlPDFDocBuilder.cc', ]) endif if cairo_opt.enabled() poppler_sources += files([ 'CairoFontEngine.cc', 'CairoOutputDev.cc', 'CairoRescaleBox.cc', ]) endif if jpeg_opt.enabled() poppler_sources += files([ 'DCTStream.cc', ]) endif if openjpeg_opt.enabled() poppler_sources += files([ 'JPEG2000Stream.cc', ]) else poppler_sources += files([ 'JPXStream.cc', ]) endif if zlib_opt.enabled() poppler_sources += files([ 'FlateEncoder.cc', ]) endif if zlib_uncompress_opt.enabled() poppler_sources += files([ 'FlateStream.cc', ]) endif if nss3_opt.enabled() poppler_source += files([ 'SignatureHandler.cc', ]) endif

Note that we use the files function, which returns a list[file] - that's right, not everything is stringly-typed in Meson!

The rest is pretty straightforward.

We declare a build target, from all those source files, with all the right include directories, specifying that that one should be installed to the prefix:

meson.build file
poppler = build_target('poppler', poppler_sources, target_type : 'static_library', dependencies: [freetype_dep], include_directories: include_directories('../internal', '.', '../goo', '..'), install: true, )

Then we declare a dependency, so other meson projects can depend on it:

meson.build file
poppler_dep = declare_dependency( link_with: poppler, include_directories : include_directories('.'), )

And then, just like glib and cairo before us, we override the poppler dependency, so that if any other meson subprojects are looking for a poppler, they use us and no one else:

meson.build file
if meson.version().version_compare('>=0.54.0') meson.override_dependency('poppler', poppler_dep) endif

And finally, we configure the .pc file:

meson.build file
configure_file( input: '../poppler.pc.cmake', output: 'poppler.pc', configuration: pkgconfig_cdata, format: 'cmake@', install_dir: poppler_pkgconfigdir, )

Which turns this:

pkg-config
prefix=@CMAKE_INSTALL_PREFIX@ libdir=@CMAKE_INSTALL_FULL_LIBDIR@ includedir=@CMAKE_INSTALL_FULL_INCLUDEDIR@ Name: poppler Description: PDF rendering library Version: @POPPLER_VERSION@ Libs: -L${libdir} -lpoppler Cflags: -I${includedir}/poppler

Into this:

pkg-config
prefix=/tmp/poppler-prefix libdir=/tmp/poppler-prefix/lib64 includedir=/tmp/poppler-prefix/include Name: poppler Description: PDF rendering library Version: 21.12.0 Libs: -L${libdir} -lpoppler Cflags: -I${includedir}/poppler

Wonderful!

All we have left is glib/meson.build, which is the glib interface to poppler (just like poppler has a QT5/QT6 interface):

It looks very similar to what we've seen so far. First we configure a third header:

meson.build file
cdata_features = configuration_data() cdata_features.set('CAIRO_FEATURE', '#define POPPLER_HAS_CAIRO 1') cdata_features.set('POPPLER_MAJOR_VERSION', poppler_major_version) cdata_features.set('POPPLER_MINOR_VERSION', poppler_minor_version) cdata_features.set('POPPLER_MICRO_VERSION', poppler_micro_version) configure_file( input: 'poppler-features.h.cmake', output: 'poppler-features.h', configuration: cdata_features, format: 'cmake@', )

Then we list our "public" headers, that will be installed into the prefix:

meson.build file
poppler_glib_public_headers = files([ 'poppler-action.h', 'poppler-date.h', 'poppler-document.h', 'poppler-page.h', 'poppler-attachment.h', 'poppler-form-field.h', 'poppler-annot.h', 'poppler-layer.h', 'poppler-movie.h', 'poppler-media.h', 'poppler.h', 'poppler-structure-element.h', ]) install_headers(poppler_glib_public_headers, subdir: poppler_includedir / 'poppler/glib')

Here's the tricky bit, and the reason we imported the gnome meson module:

meson.build file
poppler_enums = gnome.mkenums('poppler-enums', sources: poppler_glib_public_headers, c_template: 'poppler-enums.c.template', h_template: 'poppler-enums.h.template', install_dir: poppler_includedir / 'poppler/glib', install_header: true, )

This configures the poppler-enums.h.template file, that looks like this:

C code
/*** BEGIN file-header ***/ #ifndef POPPLER_ENUMS_H #define POPPLER_ENUMS_H #include <glib-object.h> #include "poppler.h" G_BEGIN_DECLS /*** END file-header ***/ /*** BEGIN file-production ***/ /* enumerations from "@filename@" */ /*** END file-production ***/ /*** BEGIN value-header ***/ POPPLER_PUBLIC GType @enum_name@_get_type (void) G_GNUC_CONST; #define POPPLER_TYPE_@ENUMSHORT@ (@enum_name@_get_type ()) /*** END value-header ***/ /*** BEGIN file-tail ***/ G_END_DECLS #endif /* !POPPLER_ENUMS_H */ /*** END file-tail ***/

Into build/subprojects/poppler-21.12.0/glib/poppler-enums.h, which looks like that:

C code
/* This file is generated by glib-mkenums, do not modify it. This code is licensed under the same license as the containing project. Note that it links to GLib, so must comply with the LGPL linking clauses. */ #ifndef POPPLER_ENUMS_H #define POPPLER_ENUMS_H #include <glib-object.h> #include "poppler.h" G_BEGIN_DECLS /* enumerations from "/home/amos/bearcove/poppler-meson/build/../subprojects/poppler-21.12.0/glib/poppler-action.h" */ POPPLER_PUBLIC GType poppler_action_type_get_type (void) G_GNUC_CONST; #define POPPLER_TYPE_ACTION_TYPE (poppler_action_type_get_type ()) POPPLER_PUBLIC GType poppler_dest_type_get_type (void) G_GNUC_CONST; #define POPPLER_TYPE_DEST_TYPE (poppler_dest_type_get_type ()) POPPLER_PUBLIC GType poppler_action_movie_operation_get_type (void) G_GNUC_CONST; #define POPPLER_TYPE_ACTION_MOVIE_OPERATION (poppler_action_movie_operation_get_type ()) POPPLER_PUBLIC GType poppler_action_layer_action_get_type (void) G_GNUC_CONST; #define POPPLER_TYPE_ACTION_LAYER_ACTION (poppler_action_layer_action_get_type ()) /* (cut.) */ #endif /* !POPPLER_ENUMS_H */ /* Generated data ends here */

Using a python3 script that parses headers like poppler's glib/poppler-action.h:

C code
#ifndef __POPPLER_ACTION_H__ #define __POPPLER_ACTION_H__ #include <glib-object.h> #include "poppler.h" G_BEGIN_DECLS /* (cut.) */ typedef enum { POPPLER_DEST_UNKNOWN, POPPLER_DEST_XYZ, POPPLER_DEST_FIT, POPPLER_DEST_FITH, POPPLER_DEST_FITV, POPPLER_DEST_FITR, POPPLER_DEST_FITB, POPPLER_DEST_FITBH, POPPLER_DEST_FITBV, POPPLER_DEST_NAMED } PopplerDestType; /* (cut.) */

That's... terrifying. They're parsing C headers from Python?

OOhhh yes.

Python
# read lines until we have no open comments while re.search(r'/\*([^*]|\*(?!/))*$', line): line += curfile.readline() # strip comments w/o options line = re.sub(r'''/\*(?!<) ([^*]+|\*(?!/))* \*/''', '', line) # ignore forward declarations if re.match(r'\s*typedef\s+enum.*;', line): continue m = re.match(r'''\s*typedef\s+enum\s*[_A-Za-z]*[_A-Za-z0-9]*\s* ({)?\s* (?:/\*< (([^*]|\*(?!/))*) >\s*\*/)? \s*({)?''', line, flags=re.X)

Why does that Python feel like Perl...

...because it used to be:

Python
#!/usr/bin/env python3 # If the code below looks horrible and unpythonic, do not panic. # # It is. # # This is a manual conversion from the original Perl script to # Python. Improvements are welcome. #

Ahhhhhhhhhhhhhhhhhhhhhhhh-

Are you having fun?

I mean, yes. This almost makes up for not really talking about vector graphics for like ten pages.

Splendid. I knew you'd like it.

Then, we list source files, including the ones we generated with glib-mkenums:

meson.build file
poppler_glib_sources = files([ 'poppler-action.cc', 'poppler-date.cc', 'poppler-document.cc', 'poppler-page.cc', 'poppler-attachment.cc', 'poppler-form-field.cc', 'poppler-annot.cc', 'poppler-layer.cc', 'poppler-movie.cc', 'poppler-media.cc', 'poppler.cc', 'poppler-cached-file-loader.cc', 'poppler-input-stream.cc', 'poppler-structure-element.cc', ]) poppler_glib_generated_sources = files([ '../poppler/CairoFontEngine.cc', '../poppler/CairoOutputDev.cc', '../poppler/CairoRescaleBox.cc', ]) poppler_glib_generated_sources += poppler_enums poppler_glib_sources += poppler_glib_generated_sources

And then, the usual dance: declare a build target (with a lot of dependencies there, chaos glib is a ladder), declare a dependency, override it...

meson.build file
poppler_glib_cpp_args = [] if cc.get_id() == 'msvc' poppler_glib_cpp_args += ['-DG_OS_WIN32=1'] endif poppler_glib = build_target('poppler-glib', poppler_glib_sources, target_type: 'static_library', cpp_args: poppler_glib_cpp_args, dependencies: [glib_dep, gobject_dep, gio_dep, cairo_dep, cairogobj_dep], include_directories: include_directories('../internal', '.', '../goo', '../poppler', '..'), install: true, ) poppler_glib_dep = declare_dependency( link_with: poppler_glib, include_directories : include_directories('.'), ) if meson.version().version_compare('>=0.54.0') meson.override_dependency('poppler-glib', poppler_glib_dep) endif

And configure the pkg-config file:

meson.build file
configure_file( input: '../poppler-glib.pc.cmake', output: 'poppler-glib.pc', configuration: pkgconfig_cdata, format: 'cmake@', install_dir: poppler_pkgconfigdir, )

And we're home free!

The next step, of course, is to build that in CI.

This article was made possible thanks to my patrons: Alexander Payne, Fredrik Østrem, David Barsky, Yufan Lou, Stephen Molyneaux, Barret Rennie, Thomas Corbin, MW, Jacob Cheriathundam, Michael Watzko, Embark Studios, Eugene Bulkin, Marcus Griep, Petar Radosevic, Tool Army, Tully, Santiago Lema, Spencer Gilbert, Jörn Huxhorn, Garrett Ward, DEX, Christian Oudard, Ronen Cohen, Thor Kamphefner, Kamran Khan, Cole Kurkowski, Arjen Laarhoven, Vicente Bosch, Chirag Jain, Ville Mattila, Marie Janssen, Vladyslav Batyrenko, Cameron Clausen, spike grobstein, Jon Gjengset, Paul Marques Mota, Jakub Fijałkowski, Mitchell Hamilton, Brad Luyster, Max von Forell, Jake S, Dimitri Merejkowsky, Chris Biscardi, René Ribaud, Alex Doroshenko, Vincent, Steven McGuire, Chad Birch, Chris Emery, Bob Ippolito, John Van Enk, metabaron, Isak Sunde Singh, Philipp Gniewosz, Mads Johansen, lukvol, Ives van Hoorne, Jan De Landtsheer, Daniel Strittmatter, Evgeniy Dubovskoy, Alex Rudy, Shane Lillie, Romet Tagobert, Douglas Creager, Corey Alexander, Molly Howell, knutwalker, Zachary Dremann, Sebastian Ziebell, Julien Roncaglia, Amber Kowalski, T, queenfartbutt, Paul Kline, Kristoffer Ström, Astrid Bek, Yoh Deadfall, Justin Ossevoort, Tomáš Duda, Jeremy Banks, Rasmus Larsen, Torben Clasen, C J Silverio, Walther, Pete Bevin, Shane Sveller, Clara Schultz, jer, Wonwoo Choi, Hawken Rives, João Veiga, Richard Pringle, Adam Perry, Benjamin Röjder Delnavaz, Matt Jadczak, Jonathan Knapp, Maximilian, Seth Stadick, brianloveswords, Sean Bryant, Ember, Sebastian Zimmer, Makoto Nakashima, Geoff Cant, Geoffroy Couprie, Michael Alyn Miller, o0Ignition0o, Zaki, Raphael Gaschignard, Romain Ruetschi, Ignacio Vergara, Pascal, Jane Lusby, Nicolas Goy, Ted Mielczarek, Aurora.

This article is part 5 of the Don't shell out! series.

Read the next part

If you liked this article, please support my work on Patreon!

Become a Patron

Looking for the homepage?
Another article: Rust modules vs files