Skip to content

Commit 7732574

Browse files
CyberShadowthewilsonator
authored andcommitted
dub.compilers.utils: Add pkg-config C preprocessor flags support
Query pkg-config for any C preprocessor flags as well (notably include paths and defines), and pass them to the D compiler so that they can be included when preprocessing C code and library headers.
1 parent 972aa35 commit 7732574

File tree

4 files changed

+109
-4
lines changed

4 files changed

+109
-4
lines changed

source/dub/compilers/utils.d

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,13 @@ void resolveLibs(ref BuildSettings settings, const scope ref BuildPlatform platf
148148
} else if (f.startsWith("-Wl,")) settings.addLFlags(f[4 .. $].split(","));
149149
else settings.addLFlags(f);
150150
}
151+
152+
// Also extract C preprocessor flags from pkg-config --cflags
153+
auto cflags = execute([pkgconfig_bin, "--cflags"] ~ pkgconfig_libs);
154+
if (cflags.status == 0) {
155+
logDiagnostic("Using pkg-config to resolve C preprocessor flags for %s.", pkgconfig_libs.join(", "));
156+
applyPkgConfigCFlags(settings, cflags.output);
157+
}
151158
}
152159
if (settings.libs.length) logDiagnostic("Using direct -l... flags for %s.", settings.libs.array.join(", "));
153160
} catch (Exception e) {
@@ -160,6 +167,68 @@ void resolveLibs(ref BuildSettings settings, const scope ref BuildPlatform platf
160167
}
161168

162169

170+
/**
171+
Parses pkg-config --cflags output and applies C preprocessor flags to build settings.
172+
173+
All C preprocessor flags (-I, -D, -U, -include, -isystem, -idirafter) are passed
174+
to the D compiler via -P prefix (for DMD/LDC) so they get forwarded to the C preprocessor.
175+
This is called from resolveLibs() which runs after cImportPaths are already processed,
176+
so we add directly to dflags with the -P prefix.
177+
*/
178+
package void applyPkgConfigCFlags(ref BuildSettings settings, string cflagsOutput)
179+
{
180+
import std.algorithm : splitter, startsWith;
181+
182+
// Note: We split by whitespace for consistency with --libs parsing above.
183+
// This won't handle shell-quoted paths with spaces, but neither does --libs.
184+
foreach (f; cflagsOutput.splitter()) {
185+
if (f.startsWith("-I") || f.startsWith("-D") || f.startsWith("-U") ||
186+
f.startsWith("-include") || f.startsWith("-isystem") ||
187+
f.startsWith("-idirafter")) {
188+
// Pass all C preprocessor flags via -P (for DMD/LDC)
189+
settings.addDFlags("-P" ~ f);
190+
}
191+
}
192+
}
193+
194+
unittest {
195+
BuildSettings settings;
196+
197+
// Test -I flag extraction (passed via -P prefix)
198+
applyPkgConfigCFlags(settings, "-I/usr/include/foo -I/usr/include/bar");
199+
assert(settings.dflags == ["-P-I/usr/include/foo", "-P-I/usr/include/bar"]);
200+
201+
// Test preprocessor define flags
202+
settings = BuildSettings.init;
203+
applyPkgConfigCFlags(settings, "-DFOO=1 -DBAR -UBAZ");
204+
assert(settings.dflags == ["-P-DFOO=1", "-P-DBAR", "-P-UBAZ"]);
205+
206+
// Test mixed flags
207+
settings = BuildSettings.init;
208+
applyPkgConfigCFlags(settings, "-I/usr/include -DVERSION=2 -isystem/usr/local/include");
209+
assert(settings.dflags == ["-P-I/usr/include", "-P-DVERSION=2", "-P-isystem/usr/local/include"]);
210+
211+
// Test -idirafter flag (attached path)
212+
settings = BuildSettings.init;
213+
applyPkgConfigCFlags(settings, "-idirafter/fallback/include");
214+
assert(settings.dflags == ["-P-idirafter/fallback/include"]);
215+
216+
// Note: -include with space-separated argument (e.g., "-include config.h") is not
217+
// fully supported - the flag is passed but the argument may be lost if space-separated.
218+
// Most pkg-config files use attached arguments (e.g., -I/path, -DFOO).
219+
220+
// Test empty input
221+
settings = BuildSettings.init;
222+
applyPkgConfigCFlags(settings, "");
223+
assert(settings.dflags.length == 0);
224+
225+
// Test unknown flags are ignored (e.g., -pthread which is for linker)
226+
settings = BuildSettings.init;
227+
applyPkgConfigCFlags(settings, "-I/inc -pthread -Wall");
228+
assert(settings.dflags == ["-P-I/inc"]);
229+
}
230+
231+
163232
/** Searches the given list of compiler flags for ones that have a generic
164233
equivalent.
165234

source/dub/generators/build.d

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -601,11 +601,22 @@ class BuildGenerator : ProjectGenerator {
601601
}
602602
if (settings.buildMode == BuildMode.singleFile && generate_binary) {
603603
import std.parallelism, std.range : walkLength;
604+
import std.algorithm : filter, startsWith;
604605

605606
auto lbuildsettings = buildsettings;
606607
auto srcs = buildsettings.sourceFiles.filter!(f => !isLinkerFile(settings.platform, f));
607608
auto objs = new string[](srcs.walkLength);
608609

610+
// Resolve pkg-config flags early to get C preprocessor flags for compilation.
611+
// We do this on lbuildsettings first, then copy -P flags to buildsettings.
612+
lbuildsettings.sourceFiles = is_static_library ? [] : lbuildsettings.sourceFiles.filter!(f => isLinkerFile(settings.platform, f)).array;
613+
settings.compiler.setTarget(lbuildsettings, settings.platform);
614+
settings.compiler.prepareBuildSettings(lbuildsettings, settings.platform, BuildSetting.commandLineSeparate|BuildSetting.sourceFiles);
615+
616+
// Copy C preprocessor flags (-P...) from linker settings to compiler settings.
617+
foreach (flag; lbuildsettings.dflags.filter!(f => f.startsWith("-P")))
618+
buildsettings.addDFlags(flag);
619+
609620
void compileSource(size_t i, string src) {
610621
logInfo("Compiling", Color.light_green, "%s", src);
611622
const objPath = pathToObjName(settings.platform, src, settings.toolWorkingDirectory);
@@ -619,9 +630,6 @@ class BuildGenerator : ProjectGenerator {
619630
}
620631

621632
logInfo("Linking", Color.light_green, "%s", buildsettings.targetName.color(Mode.bold));
622-
lbuildsettings.sourceFiles = is_static_library ? [] : lbuildsettings.sourceFiles.filter!(f => isLinkerFile(settings.platform, f)).array;
623-
settings.compiler.setTarget(lbuildsettings, settings.platform);
624-
settings.compiler.prepareBuildSettings(lbuildsettings, settings.platform, BuildSetting.commandLineSeparate|BuildSetting.sourceFiles);
625633
settings.compiler.invokeLinker(lbuildsettings, settings.platform, objs, settings.linkCallback, settings.toolWorkingDirectory);
626634

627635
// NOTE: separate compile/link is not yet enabled for GDC.
@@ -646,6 +654,13 @@ class BuildGenerator : ProjectGenerator {
646654
if (generate_binary) settings.compiler.setTarget(lbuildsettings, settings.platform);
647655
settings.compiler.prepareBuildSettings(lbuildsettings, settings.platform, BuildSetting.commandLineSeparate|BuildSetting.sourceFiles);
648656

657+
// Copy C preprocessor flags (-P...) from linker settings to compiler settings.
658+
// These come from pkg-config --cflags and are needed for ImportC compilation.
659+
// We must copy them before clearing libs, since resolveLibs extracts them.
660+
import std.algorithm : filter, startsWith;
661+
foreach (flag; lbuildsettings.dflags.filter!(f => f.startsWith("-P")))
662+
buildsettings.addDFlags(flag);
663+
649664
// setup compiler command line
650665
buildsettings.libs = null;
651666
buildsettings.frameworks = null;

test/issue782-gtkd-pkg-config.sh

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,25 @@ else
2222
export LD_LIBRARY_PATH=${LD_LIBRARY_PATH:-}${LD_LIBRARY_PATH:+:}$PWD/../fake-gtkd
2323
# pkg-config needs to find our .pc file which is in $PWD/../fake-gtkd/pkgconfig, so set PKG_CONFIG_PATH accordingly
2424
export PKG_CONFIG_PATH=$PWD/../fake-gtkd/pkgconfig
25+
26+
# Verify that pkg-config --cflags is being extracted properly
27+
# The .pc file includes -DFAKE_GTKD_VERSION=100 which should appear as -P-DFAKE_GTKD_VERSION=100
28+
VERBOSE_OUTPUT=$(${DUB} build --force --compiler=${DC} -v 2>&1)
29+
if ! echo "$VERBOSE_OUTPUT" | grep -q "\-P-DFAKE_GTKD_VERSION=100"; then
30+
echo "FAIL: pkg-config --cflags extraction not working: -P-DFAKE_GTKD_VERSION=100 not found in compiler flags"
31+
echo "Verbose output:"
32+
echo "$VERBOSE_OUTPUT"
33+
exit 1
34+
fi
35+
echo "PASS: pkg-config --cflags extraction working (-P-DFAKE_GTKD_VERSION=100 found)"
36+
37+
# Also verify -P-I flag for cImportPaths is present
38+
if ! echo "$VERBOSE_OUTPUT" | grep -q "\-P-I.*fake-gtkd"; then
39+
echo "FAIL: pkg-config --cflags extraction not working: -P-I path not found in compiler flags"
40+
exit 1
41+
fi
42+
echo "PASS: pkg-config --cflags include path extraction working"
43+
2544
${DUB} run --force --compiler=${DC}
2645
cd ..
2746
rm -rf fake-gtkd/.dub

test/issue782-gtkd-pkg-config/fake-gtkd/pkgconfig/fake-gtkd.pc

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,6 @@ Version: 1.0.0
99
# The "-L-defaultlib=libphobos2.so" and "-defaultlib=libphobos2.so" should both end up on the compiler (at link stage) invocation as "-defaultlib=libphobos2.so"
1010
# For this test, it doesn't hurt that they appear twice on the cmd line...
1111
Libs: -L-L${libdir} -L-l:libfake-gtkd.so -L-l:libdl.so.2 -pthread -L-defaultlib=libphobos2.so -defaultlib=libphobos2.so
12-
Cflags: -I${includedir}
12+
# Cflags should be extracted and passed to the C preprocessor via -P flags
13+
# -I goes to cImportPaths, -D goes to dflags with -P prefix
14+
Cflags: -I${includedir} -DFAKE_GTKD_VERSION=100

0 commit comments

Comments
 (0)