Two Files Called config.h
Both vendored libraries expect a private config.h, and the two are nothing alike. libmypaint's is three lines Ariandel wrote by hand (you'll see the whole file below). json-c's is upstream CMake's output for iOS: 236 lines and 64 #define directives, from HAVE_DLFCN_H down to the version string.
Here's the collision hiding in plain sight. libmypaint's file says #define PACKAGE "libmypaint". json-c's says #define PACKAGE "json-c". Same macro, two values, and neither file is wrong. Each one is a fact about building one library. The only way to get it wrong is to let one library see the other's facts.
libmypaint already draws this line itself. Its public tunables (MYPAINT_TILE_SIZE 64, maximum threads, mipmap levels) live in mypaint-config.h, a public header in include/. The private build switches live in config.h. The filename tells you the audience.
Where Each One Lives
Both files sit at their target's root, next to the .c files, and never in include/. That placement is the whole design, because in SwiftPM those two directories mean very different things:
- A C target's
include/is its public face. It goes on the header search path of every target that depends on it, and it becomes the Clang module that Swift code imports. - The target's root is private.
.headerSearchPath(".")adds it to that target's own search path and to nobody else's.
The C preprocessor helps here too: the quote form, #include "config.h", looks first in the directory of the file doing the including, and only then down the -I list. A .c file sitting beside its own config.h finds it before any other path gets a vote. (CMyPaint also defines HAVE_CONFIG_H, the autotools convention. At d5a88fb libmypaint's sources include config.h unconditionally, so location is what actually decides which file they get.)
Ariandel's house sign states the rule as a gotcha: "libmypaint's config.h and json-c's config.h are different files in different targets; never put either in a target's public include/, or Swift imports the wrong macros."
Watching It Go Wrong
Rules like that are easier to keep once you've watched them break, so the sketch below builds the mistake on purpose. Two C targets, CJson and CPaint, with CPaint depending on CJson. Each has a config.h defining PACKAGE, and each exports one function that returns it. The script builds two layouts.
- Leak (each
config.hin its target'sinclude/) printsjson-c json-c.CPaint's own code compiled with json-c's value. No error, no warning. - Fixed (each
config.hat its target root) printslibmypaint json-c.
Why does the leak go that way? On the toolchain I ran (Swift 6.4, Xcode 27), CPaint's compile line lists its dependency's public include/ first, then its own include/, then its own root. paint.c has no config.h beside it in the leak layout, so the -I order decides, and the dependency wins.
Swift sees it too. Add print(PACKAGE) to the Swift file: in the leak layout the build fails with "ambiguous use of 'PACKAGE'", because both modules export the macro. In the fixed layout it fails with "cannot find 'PACKAGE' in scope", which is exactly what you want. A private build fact should be invisible to Swift.
The demo's quiet version is the scary one. Nothing failed, the program ran, and it was wrong. Swap PACKAGE for a macro that switches a feature on and that same silence ships a different program.
The Search Path Is a Design Surface
Treat include/ like an API, because SwiftPM does. Everything in it is promised to every dependent, the app's Swift included. Ariandel's own bridge shows the discipline: CAriandelSurface/include/ holds exactly four headers (ariandel_surface.h, ariandel_kernels.h, ariandel_pixels.h, ariandel_shader_types.h), each one something the Swift engine is meant to call or share. Build facts stay home.
Three Licenses, No GPL Code
- libmypaint: ISC. Permissive: use, copy, modify, distribute, as long as the notice comes along. Its
COPYINGsits in the vendored directory. - json-c: MIT. The same spirit, the same kind of notice.
- Brushes: CC0. The app bundles the mypaint-brushes library at
08da4a4: 196 presets across seven sets, each with its preview, plusorder.conf, 393 files byte-identical to upstream.
The brushes deserve a second look, because their license file contains the letters GPL. It's upstream's machine-readable copyright file, in Debian's DEP-5 format, copied into the app as MYPAINT-BRUSHES-LICENSES.dep5. It opens with Files: * under GPL-2+, then Files: brushes/* under CC0-1.0, then the per-author brush directories, also CC0. DEP-5's rule is that the last stanza matching a file applies to it. So every preset and preview Ariandel ships is CC0, and the catch-all GPL-2+ covers upstream's build scripts, which were never copied. The vendored libmypaint and json-c files don't mention the GPL anywhere. The exclude lists in Package.swift keep COPYING and PROVENANCE.md out of the compile (they're documents, not sources) while leaving them beside the code they cover.
include/ means "every dependent, and Swift"; the target root means "this target only." Put each fact where its audience is, and two libraries can carry the same macro name without ever meeting.