1 module served.utils.stdlib_detect; 2 3 import std.algorithm : countUntil, splitter, startsWith; 4 import std.array : appender, replace; 5 import std.ascii : isWhite; 6 import std.conv : to; 7 import std.path : buildNormalizedPath, buildPath, chainPath, dirName; 8 import std.process : environment; 9 import std..string : indexOf, startsWith, strip, stripLeft; 10 import std.uni : sicmp; 11 import std.utf : decode, UseReplacementDchar; 12 13 import fs = std.file; 14 import io = std.stdio; 15 16 string[] autoDetectStdlibPaths(string cwd = null) 17 { 18 string[] ret; 19 if (detectDMDStdlibPaths(cwd, ret) || detectLDCStdlibPaths(cwd, ret)) 20 { 21 return ret; 22 } 23 else 24 { 25 version (Windows) 26 return [`C:\D\dmd2\src\druntime\import`, `C:\D\dmd2\src\phobos`]; 27 else version (OSX) 28 return [`/Library/D/dmd/src/druntime/import`, `/Library/D/dmd/src/phobos`]; 29 else version (Posix) 30 return [`/usr/include/dmd/druntime/import`, `/usr/include/dmd/phobos`]; 31 else 32 { 33 pragma(msg, __FILE__ ~ "(" ~ __LINE__ 34 ~ "): Note: Unknown target OS. Please add default D stdlib path"); 35 return []; 36 } 37 } 38 } 39 40 bool detectDMDStdlibPaths(string cwd, out string[] ret) 41 { 42 // https://dlang.org/dmd-linux.html#dmd-conf 43 // https://dlang.org/dmd-osx.html#dmd-conf 44 // https://dlang.org/dmd-windows.html#sc-ini 45 46 version (Windows) 47 { 48 static immutable confName = "sc.ini"; 49 static immutable dmdExe = "dmd.exe"; 50 } 51 else 52 { 53 static immutable confName = "dmd.conf"; 54 static immutable dmdExe = "dmd"; 55 } 56 57 if (cwd.length && fs.exists(chainPath(cwd, confName)) 58 && parseDmdConfImports(buildPath(cwd, confName), cwd, ret)) 59 return true; 60 61 string home = environment.get("HOME"); 62 if (home.length && fs.exists(chainPath(home, confName)) 63 && parseDmdConfImports(buildPath(home, confName), home, ret)) 64 return true; 65 66 auto dmdPath = searchPathFor(dmdExe); 67 if (dmdPath.length) 68 { 69 auto dmdDir = dirName(dmdPath); 70 if (fs.exists(chainPath(dmdDir, confName)) 71 && parseDmdConfImports(buildPath(dmdDir, confName), dmdDir, ret)) 72 return true; 73 } 74 75 version (Windows) 76 { 77 if (dmdPath.length) 78 { 79 auto dmdDir = dirName(dmdPath); 80 bool haveDRuntime = fs.exists(chainPath(dmdDir, "..", "..", "src", 81 "druntime", "import")); 82 bool havePhobos = fs.exists(chainPath(dmdDir, "..", "..", "src", "phobos")); 83 if (haveDRuntime && havePhobos) 84 ret = [ 85 buildNormalizedPath(dmdDir, "..", "..", "src", "druntime", 86 "import"), 87 buildNormalizedPath(dmdDir, "..", "..", "src", "phobos") 88 ]; 89 else if (haveDRuntime) 90 ret = [ 91 buildNormalizedPath(dmdDir, "..", "..", "src", "druntime", "import") 92 ]; 93 else if (havePhobos) 94 ret = [buildNormalizedPath(dmdDir, "..", "..", "src", "phobos")]; 95 96 return ret.length > 0; 97 } 98 else 99 { 100 return false; 101 } 102 } 103 else version (Posix) 104 { 105 if (fs.exists("/etc/dmd.conf") && parseDmdConfImports("/etc/dmd.conf", "/etc", ret)) 106 return true; 107 108 return false; 109 } 110 else 111 { 112 pragma(msg, 113 __FILE__ ~ "(" ~ __LINE__ 114 ~ "): Note: Unknown target OS. Please add default dmd stdlib path"); 115 return false; 116 } 117 } 118 119 bool detectLDCStdlibPaths(string cwd, out string[] ret) 120 { 121 // https://github.com/ldc-developers/ldc/blob/829dc71114eaf7c769208f03eb9a614dafd789c3/driver/configfile.cpp 122 123 static bool tryPath(R)(R path, lazy scope const(char)[] pathDir, out string[] ret) 124 { 125 return fs.exists(path) && parseLdcConfImports(path.to!string, pathDir, ret); 126 } 127 128 static immutable confName = "ldc2.conf"; 129 version (Windows) 130 { 131 static immutable ldcExe = "ldc2.exe"; 132 static immutable ldcExeAlt = "ldc.exe"; 133 } 134 else 135 { 136 static immutable ldcExe = "ldc2"; 137 static immutable ldcExeAlt = "ldc"; 138 } 139 140 auto ldcPath = searchPathFor(ldcExe); 141 if (!ldcPath.length) 142 ldcPath = searchPathFor(ldcExeAlt); 143 auto ldcDir = ldcPath.length ? dirName(ldcPath) : null; 144 145 if (tryPath(chainPath(cwd, confName), ldcDir, ret)) 146 return true; 147 148 if (ldcPath.length) 149 { 150 if (tryPath(chainPath(ldcDir, confName), ldcDir, ret)) 151 return true; 152 } 153 154 string home = getUserHomeDirectoryLDC(); 155 if (home.length) 156 { 157 if (tryPath(chainPath(home, ".ldc", confName), ldcDir, ret)) 158 return true; 159 160 version (Windows) 161 { 162 if (tryPath(chainPath(home, confName), ldcDir, ret)) 163 return true; 164 } 165 } 166 167 if (ldcPath.length) 168 { 169 if (tryPath(chainPath(ldcDir.dirName, "etc", confName), ldcDir, ret)) 170 return true; 171 } 172 173 version (Windows) 174 { 175 string path = readLdcPathFromRegistry(); 176 if (path.length && tryPath(chainPath(path, "etc", confName), ldcDir, ret)) 177 return true; 178 } 179 else 180 { 181 if (tryPath(chainPath("/etc", confName), ldcDir, ret)) 182 return true; 183 if (tryPath(chainPath("/etc/ldc", confName), ldcDir, ret)) 184 return true; 185 } 186 187 return false; 188 } 189 190 version (Windows) private string readLdcPathFromRegistry() 191 { 192 import std.windows.registry; 193 194 // https://github.com/ldc-developers/ldc/blob/829dc71114eaf7c769208f03eb9a614dafd789c3/driver/configfile.cpp#L65 195 try 196 { 197 scope Key hklm = Registry.localMachine; 198 scope Key val = hklm.getKey(`SOFTWARE\ldc-developers\LDC\0.11.0`, REGSAM.KEY_QUERY_VALUE); 199 return val.getValue("Path").value_SZ(); 200 } 201 catch (RegistryException) 202 { 203 return null; 204 } 205 } 206 207 private string getUserHomeDirectoryLDC() 208 { 209 version (Windows) 210 { 211 import core.sys.windows.windows; 212 import core.sys.windows.shlobj; 213 214 wchar[MAX_PATH] buf; 215 HRESULT res = SHGetFolderPathW(null, CSIDL_FLAG_CREATE | CSIDL_APPDATA, null, SHGFP_TYPE 216 .SHGFP_TYPE_CURRENT, buf.ptr); 217 if (res != S_OK) 218 return null; 219 220 auto len = buf[].countUntil(wchar('\0')); 221 if (len == -1) 222 len = buf.length; 223 return buf[0 .. len].to!string; 224 } 225 else 226 { 227 string home = environment.get("HOME"); 228 return home.length ? home : "/"; 229 } 230 } 231 232 private string searchPathFor()(scope const(char)[] executable) 233 { 234 auto path = environment.get("PATH"); 235 236 version (Posix) 237 char separator = ':'; 238 else version (Windows) 239 char separator = ';'; 240 else 241 static assert(false, "No path separator character"); 242 243 foreach (dir; path.splitter(separator)) 244 { 245 auto execPath = buildPath(dir, executable); 246 if (fs.exists(execPath)) 247 return execPath; 248 } 249 250 return null; 251 } 252 253 deprecated bool parseDmdConfImports(R)(R path, out string[] paths) 254 { 255 return parseDmdConfImports(path, dirName(path).to!string, paths); 256 } 257 258 bool parseDmdConfImports(R)(R confPath, scope const(char)[] confDirPath, out string[] paths) 259 { 260 enum Region 261 { 262 none, 263 env32, 264 env64 265 } 266 267 Region match, current; 268 269 foreach (line; io.File(confPath).byLine) 270 { 271 line = line.strip; 272 if (!line.length) 273 continue; 274 275 if (line.sicmp("[Environment32]") == 0) 276 current = Region.env32; 277 else if (line.sicmp("[Environment64]") == 0) 278 current = Region.env64; 279 else if (line.startsWith("DFLAGS=") && current >= match) 280 { 281 version (Windows) 282 paths = parseDflagsImports(line["DFLAGS=".length .. $].stripLeft, confDirPath, true); 283 else 284 paths = parseDflagsImports(line["DFLAGS=".length .. $] 285 .stripLeft, confDirPath, false); 286 match = current; 287 } 288 } 289 290 return match != Region.none || paths.length > 0; 291 } 292 293 bool parseLdcConfImports(string confPath, scope const(char)[] binDirPath, out string[] paths) 294 { 295 import external.ldc.config; 296 297 auto ret = appender!(string[]); 298 299 binDirPath = binDirPath.replace('\\', '/'); 300 301 void handleSwitch(string value) 302 { 303 if (value.startsWith("-I")) 304 ret ~= value[2 .. $]; 305 } 306 307 void parseSection(GroupSetting section) 308 { 309 foreach (c; section.children) 310 { 311 if (c.type == Setting.Type.array 312 && (c.name == "switches" || c.name == "post-switches")) 313 { 314 if (auto input = cast(ArraySetting) c) 315 { 316 foreach (sw; input.vals) 317 handleSwitch(sw.replace("%%ldcbinarypath%%", binDirPath)); 318 } 319 } 320 } 321 } 322 323 foreach (s; parseConfigFile(confPath)) 324 { 325 if (s.type == Setting.Type.group && s.name == "default") 326 { 327 parseSection(cast(GroupSetting) s); 328 } 329 } 330 331 paths = ret.data; 332 return ret.data.length > 0; 333 } 334 335 deprecated string[] parseDflagsImports(scope const(char)[] options, bool windows) 336 { 337 return parseDflagsImports(options, null, windows); 338 } 339 340 string[] parseDflagsImports(scope const(char)[] options, scope const(char)[] cwd, bool windows) 341 { 342 auto ret = appender!(string[]); 343 size_t i = options.indexOf("-I"); 344 while (i != cast(size_t)-1) 345 { 346 if (i == 0 || options[i - 1] == '"' || options[i - 1] == '\'' || options[i - 1] == ' ') 347 { 348 dchar quote = i == 0 ? ' ' : options[i - 1]; 349 i += "-I".length; 350 ret.put(parseArgumentWord(options, quote, i, windows).replace("%@P%", cwd)); 351 } 352 else 353 i += "-I".length; 354 355 i = options.indexOf("-I", i); 356 } 357 return ret.data; 358 } 359 360 private string parseArgumentWord(const scope char[] data, dchar quote, ref size_t i, bool windows) 361 { 362 bool allowEscapes = quote != '\''; 363 bool inEscape; 364 bool ending = quote == ' '; 365 auto part = appender!string; 366 while (i < data.length) 367 { 368 auto c = decode!(UseReplacementDchar.yes)(data, i); 369 if (inEscape) 370 { 371 part.put(c); 372 inEscape = false; 373 } 374 else if (ending) 375 { 376 // -I"abc"def 377 // or 378 // -I'abc'\''def' 379 if (c.isWhite) 380 break; 381 else if (c == '\\' && !windows) 382 inEscape = true; 383 else if (c == '\'') 384 { 385 quote = c; 386 allowEscapes = false; 387 ending = false; 388 } 389 else if (c == '"') 390 { 391 quote = c; 392 allowEscapes = true; 393 ending = false; 394 } 395 else 396 part.put(c); 397 } 398 else 399 { 400 if (c == quote) 401 ending = true; 402 else if (c == '\\' && allowEscapes && !windows) 403 inEscape = true; 404 else 405 part.put(c); 406 } 407 } 408 return part.data; 409 } 410 411 unittest 412 { 413 void test(string input, string[] expect) 414 { 415 auto actual = parseDflagsImports(input, false); 416 assert(actual == expect, actual.to!string ~ " != " ~ expect.to!string); 417 } 418 419 test(`a`, []); 420 test(`-I`, [``]); 421 test(`-Iabc`, [`abc`]); 422 test(`-Iab\\cd -Ief`, [`ab\cd`, `ef`]); 423 test(`-Iab\ cd -Ief`, [`ab cd`, `ef`]); 424 test(`-I/usr/include/dmd/phobos -I/usr/include/dmd/druntime/import -L-L/usr/lib/x86_64-linux-gnu -L--export-dynamic -fPIC`, 425 [`/usr/include/dmd/phobos`, `/usr/include/dmd/druntime/import`]); 426 test(`-I/usr/include/dmd/phobos -L-L/usr/lib/x86_64-linux-gnu -I/usr/include/dmd/druntime/import -L--export-dynamic -fPIC`, 427 [`/usr/include/dmd/phobos`, `/usr/include/dmd/druntime/import`]); 428 }