Commit 7e6c915
committed
On macOS, limit symbols exported by extension modules linking FreeType.
In my experience, mplcairo has only ever worked on macOS if imported
prior to matplotlib (which means that in particular just setting the
backend rcParam will not work, including via matplotlibrc), or (I have
recently discovered) if using a non-local-freetype version build of
ft2font. If none of these conditions are not satisfied, various bad
things happen, such as segfaults at runtime. I realized recently the
(one?) reason for this problem. Below, I only consider the case of
local-freetype builds (the only problematic case).
C-level cairo functions call FreeType for text rendering, and there are
two different libfreetype libraries involved: the one statically linked
into matplotlib's ft2font, and the OS-level one (which comes into play
even for local-freetype builds because libcairo.so is always provided at
the system level). Note that I am actually happy that libcairo can use
the system-level freetype, because it is typically more recent and lets
mplcairo offer e.g. better color font (emoji, etc.) support.
On Linux, I find that the system-level libcairo.so indeed loads all the
FreeType symbols it needs from the system-level libfreetype.so, so the
libfreetype.a statically linked into ft2font is completely hidden to it;
things are fine. On macOS, OTOH, I find that the libfreetype.a symbols
"leak" out of the extension module, and libcairo.so picks up some
symbols from it (one can check this via dladdr()), but (likely) not all,
which means that two different versions of of libfreetype try to access
the same FT_Face objects with (likely) incompatible ABIs, hence the
segfaults.
This difference in symbol priority likely comes down to difference in
behavior in the macOS and Linux dynamic loaders, which I have not
investigated more. However, one solution to fix is is to explicitly
limit the symbols exported by ft2font to the only one needed as an
extension module, i.e. PyInit_ft2font, hiding in particular all FreeType
symbols. This also needs to be done for _backend_agg, which also links
FreeType. To do so, I rely on the linker's -exported_symbol flag, as
explained e.g. at https://stackoverflow.com/questions/2222162; also note
that the symbol name is mangled with a leading underscore
(https://news.ycombinator.com/item?id=20143037). I can confirm that
with this fix, mplcairo can be correctly used even if imported after
a matplotlib that uses a local-freetype build, and ft2font's FreeType
symbols no longer leak-out.
I am actually not very happy about limiting symbol visibility, because
letting the linked library's symbols leak out allows various useful
tricks: mplcairo itself actually gets its cairo symbols out of pycairo,
and I have likewise previously written C-extensions that access FFTW by
stealing the symbols from pyFFTW; in both cases the advantage is to not
actually have to link the library against libcairo.so/libfftw3.so and
instead push the responsibility of packaging these libraries for the
Python ecosystem to pycairo/pyFFTW. I guess one key difference here,
though, is that pycairo/pyFFTW exist exactly to provide bindings to
these libraries, whereas ft2font isn't really there to provide bindings
to FreeType (there's instead freetype-py, for example, for that).
Still, if someone has a better understanding of the macOS dynamic loader
and knows how to fix this better, I am all ears.1 parent 6820797 commit 7e6c915
1 file changed
+4
-0
lines changed| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
593 | 593 | | |
594 | 594 | | |
595 | 595 | | |
| 596 | + | |
| 597 | + | |
| 598 | + | |
| 599 | + | |
596 | 600 | | |
597 | 601 | | |
598 | 602 | | |
| |||
0 commit comments