1 module pkg_config; 2 3 auto pkgConfig(in string lib, in string minVersion=null) 4 { 5 PkgConfig pc; 6 pc._lib = lib; 7 pc._minVersion = minVersion; 8 return pc; 9 } 10 11 version (DubPR1453) { 12 13 bool pkgConfigDubLines(in string lib, in string minVersion=null) 14 { 15 try { 16 auto pc = pkgConfig(lib, minVersion) 17 .libs() 18 .msvc(); 19 auto library = pc.invoke(); 20 library.echoDubLines(); 21 return true; 22 } 23 catch (Exception ex) { 24 import std.stdio : stderr; 25 stderr.writeln(ex.msg); 26 return false; 27 } 28 } 29 30 version(linux) unittest { 31 import std.exception : assertNotThrown; 32 assert(pkgConfigDubLines("libpng", "1.6.0")); 33 assert(!pkgConfigDubLines("libpng", "99.0")); 34 } 35 36 } 37 38 39 class LibraryNotFound : Exception { 40 this (string msg) { 41 super(msg); 42 } 43 } 44 45 struct Library 46 { 47 import std.stdio : File, stdout; 48 49 /// the name of the library 50 string name; 51 /// the version of the library 52 string ver; 53 /// all C flags verbatim 54 string[] cflags; 55 /// include paths to look for headers 56 string[] includePaths; 57 /// preprocessor definitions 58 string[] defines; 59 /// C flags that are not include paths or defines 60 string[] otherCFlags; 61 /// all link flags verbatim 62 string[] lflags; 63 /// libraries search paths 64 string[] libPaths; 65 /// libraries to link 66 string[] libs; 67 /// other link flags that are not searchs path and not libraries 68 string[] otherLFlags; 69 70 version (DubPR1453) void echoDubLines(File f=stdout) 71 { 72 import std.algorithm : map; 73 import std.array : join; 74 import std.format : format; 75 76 if (libs.length) 77 f.writeln( 78 "dub:sdl:libs ", 79 libs.map!(l => format("\"%s\"", l)).join(" ") 80 ); 81 if (libPaths.length) 82 f.writeln( 83 "dub:sdl:lflags ", 84 libPaths.map!(p => format("\"%s\"", p)).join(" ") 85 ); 86 if (otherLFlags) 87 f.writeln( 88 "dub:sdl:lflags ", 89 otherLFlags.map!(f => format("\"%s\"", f)).join(" ") 90 ); 91 } 92 93 private void parseCFlags(string flagStr) 94 { 95 import std.algorithm : startsWith; 96 97 const flags = splitFlags(flagStr); 98 99 cflags ~= flags; 100 101 foreach (f; flags) { 102 if (f.startsWith("-I")) { 103 includePaths ~= f[2 .. $]; 104 } 105 else if (f.startsWith("-D")) { 106 defines ~= f[2 .. $]; 107 } 108 else { 109 otherCFlags ~= f; 110 } 111 } 112 } 113 114 private void parseLFlags(string flagStr) 115 { 116 import std.algorithm : startsWith; 117 118 const flags = splitFlags(flagStr); 119 120 lflags ~= flags; 121 122 foreach (f; flags) { 123 if (f.startsWith("-L")) { 124 libPaths ~= f[2 .. $]; 125 } 126 else if (f.startsWith("-l")) { 127 libs ~= f[2 .. $]; 128 } 129 else { 130 otherLFlags ~= f; 131 } 132 } 133 } 134 } 135 136 struct PkgConfig 137 { 138 private string _lib; 139 private string _minVersion; 140 private string _exe = defaultExe; 141 private string[] _pkgConfigPath; 142 private bool _cflags; 143 private bool _libs; 144 private bool _systemLibs; 145 private bool _static; 146 version(Windows) private bool _msvc; 147 148 PkgConfig exe(in string exe) { 149 _exe = exe; 150 return this; 151 } 152 PkgConfig pkgConfigPath(in string path) { 153 _pkgConfigPath = [ path ]; 154 return this; 155 } 156 PkgConfig pkgConfigPath(in string[] paths) { 157 _pkgConfigPath = paths.dup; 158 return this; 159 } 160 PkgConfig cflags() { 161 _cflags = true; 162 return this; 163 } 164 PkgConfig libs() { 165 _libs = true; 166 return this; 167 } 168 PkgConfig systemLibs() { 169 _systemLibs = true; 170 return this; 171 } 172 PkgConfig staticLib() { 173 _static = true; 174 return this; 175 } 176 PkgConfig msvc() { 177 version(Windows) { 178 _msvc = true; 179 } 180 return this; 181 } 182 183 Library invoke() 184 { 185 auto env = buildCmdEnv(); 186 187 // checking for package support 188 run!LibraryNotFound(["--exists"], env); 189 190 Library lib; 191 lib.name = _lib; 192 lib.ver = run!Exception(["--modversion"], env); 193 if (_cflags) { 194 lib.parseCFlags(run!Exception(["--cflags"], env)); 195 } 196 if (_libs) { 197 lib.parseLFlags(run!Exception(["--libs"], env)); 198 } 199 return lib; 200 } 201 202 private string[string] buildCmdEnv() 203 { 204 string[string] env; 205 if (_pkgConfigPath) { 206 import std.array : join; 207 import std.process : environment; 208 string path = _pkgConfigPath.join(pathEnvSep); 209 const curVal = environment.get("PKG_CONFIG_PATH", null); 210 if (curVal) { 211 path ~= pathEnvSep ~ curVal; 212 } 213 env["PKG_CONFIG_PATH"] = path; 214 } 215 if (_systemLibs) { 216 env["PKG_CONFIG_ALLOW_SYSTEM_LIBS"] = "1"; 217 } 218 return env; 219 } 220 221 private string run (Ex = Exception)(string[] args, string[string] env) 222 { 223 import std.array : join; 224 import std.process : Config, pipe, spawnProcess, wait; 225 import std.stdio : stdin, stderr; 226 import std..string : strip; 227 228 auto p = pipe(); 229 auto cmdArgs = [ _exe, "--print-errors", "--errors-to-stdout"]; 230 if (_static) { 231 cmdArgs ~= "--static"; 232 } 233 version(Windows) if (_msvc) { 234 cmdArgs ~= "--msvc-syntax"; 235 } 236 cmdArgs ~= args; 237 if (_minVersion) { 238 cmdArgs ~= (_lib ~ " >= " ~ _minVersion); 239 } 240 else { 241 cmdArgs ~= _lib; 242 } 243 auto pid = spawnProcess(cmdArgs, stdin, p.writeEnd, stderr, env); 244 string output = p.readEnd.byLine().join("\n").strip().idup; 245 if (wait(pid)) { 246 throw new Ex(output); 247 } 248 return output; 249 } 250 } 251 252 private: 253 254 string[] splitFlags(string flagStr) pure 255 { 256 import std.uni : isSpace; 257 string[] flags; 258 string flag; 259 bool escaped; 260 foreach(c; flagStr) { 261 if (escaped) { 262 escaped = false; 263 flag ~= c; 264 } 265 else { 266 if (c == '\\') { 267 escaped = true; 268 } 269 else if (isSpace(c)) { 270 if (flag.length) { 271 flags ~= flag; 272 flag.length = 0; 273 } 274 } 275 else { 276 flag ~= c; 277 } 278 } 279 } 280 if (flag.length) flags ~= flag; 281 return flags; 282 } 283 284 version(Windows) { 285 enum exeExt = ".exe"; 286 enum pathEnvSep = ";"; 287 } 288 else { 289 enum exeExt = ""; 290 enum pathEnvSep = ":"; 291 } 292 293 enum defaultExe = "pkg-config" ~ exeExt;