1 /******************************************************************************* 2 * Manage configs and commands 3 * 4 * Mediation of gendoc configuration file and command line arguments. 5 * Also get dub package informations. 6 */ 7 module gendoc.config; 8 9 import dub.dub, dub.project, dub.package_, dub.generators.generator, dub.compilers.compiler; 10 import dub.internal.vibecompat.core.log, dub.internal.vibecompat.data.json, dub.internal.vibecompat.inet.path; 11 12 /******************************************************************************* 13 * 14 */ 15 struct PackageConfig 16 { 17 /// 18 string path; 19 /// 20 string name; 21 /// 22 string[] files; 23 /// 24 string[] options; 25 /// 26 string packageVersion; 27 /// 28 PackageConfig[] subPackages; 29 30 private static string determineConfigName(Package pkg, BuildPlatform platform, string configName) 31 { 32 import std.algorithm: canFind; 33 import std.exception: enforce; 34 auto allCfgs = pkg.getPlatformConfigurations(platform, true); 35 if (configName.length > 0) 36 { 37 enforce(allCfgs.canFind(configName), "Cannot found configuration: " ~ configName); 38 return configName; 39 } 40 if (allCfgs.canFind("gendoc")) 41 return "gendoc"; 42 return pkg.getDefaultConfiguration(platform, true); 43 } 44 45 /*************************************************************************** 46 * 47 */ 48 void loadPackage( 49 Dub dub, 50 Package pkg, 51 string archType, 52 string buildType, 53 string configName, 54 string compiler) 55 { 56 import std.algorithm, std.array, std.file, std.path; 57 Package bkupPkg = dub.project.rootPackage; 58 if (pkg) 59 dub.loadPackage(pkg); 60 scope (exit) if (pkg) 61 dub.loadPackage(bkupPkg); 62 if (!dub.project.hasAllDependencies) 63 dub.upgrade(UpgradeOptions.select); 64 65 dub.project.validate(); 66 67 auto compilerData = getCompiler(compiler); 68 BuildSettings buildSettings; 69 auto buildPlatform = compilerData.determinePlatform(buildSettings, compiler, archType); 70 71 GeneratorSettings settings; 72 settings.config = determineConfigName(pkg ? pkg : dub.project.rootPackage, buildPlatform, configName); 73 settings.force = true; 74 settings.buildType = buildType; 75 settings.compiler = compilerData; 76 settings.platform = buildPlatform; 77 name = pkg ? pkg.name : dub.project.rootPackage.name; 78 if (dub.project.rootPackage.getBuildSettings(settings.config).targetType != TargetType.none) 79 { 80 auto lists = dub.project.listBuildSettings(settings, 81 ["dflags", "versions", "debug-versions", 82 "import-paths", "string-import-paths", "options", 83 "source-files"], 84 ListBuildSettingsFormat.commandLineNul).map!(a => a.split("\0")).array; 85 // -oq, -od が付与されているゴミが紛れ込む場合がある。dubのバグか?回避する。 86 auto importDirs = lists[3].filter!(a => a.startsWith("-I") && a[2..$].exists); 87 path = importDirs.empty 88 ? pkg ? pkg.path.toNativeString() : dub.project.rootPackage.path.toNativeString() 89 : importDirs.front[2..$]; 90 options = lists[0].reduce!((a, b) => a.canFind(b) ? a : a ~ [b])(lists[1..6].join); 91 files = lists[6].filter!(a => a.exists && canFind([".d", ".dd", ".di"], a.extension)).array; 92 packageVersion = pkg 93 ? pkg.version_.toString() 94 : dub.project.rootPackage.version_.toString(); 95 } 96 foreach (spkg; dub.project.rootPackage.subPackages) 97 { 98 PackageConfig pkgcfg; 99 if (spkg.recipe.name.length > 0) 100 { 101 auto basepkg = packageVersion.length > 0 102 ? dub.packageManager.getPackage(name, packageVersion) 103 : dub.packageManager.getLatestPackage(name); 104 auto subpkg = dub.packageManager.getSubPackage(basepkg, spkg.recipe.name, false); 105 pkgcfg.loadPackage(dub, subpkg, 106 archType, buildType, configName, compiler); 107 } 108 else 109 { 110 auto tmppkgpath = dub.rootPath ~ NativePath(spkg.path); 111 auto subpkg = dub.packageManager.getOrLoadPackage(tmppkgpath, NativePath.init, true); 112 pkgcfg.loadPackage(dub, subpkg, 113 archType, buildType, configName, compiler); 114 } 115 subPackages ~= pkgcfg; 116 } 117 } 118 119 /// ditto 120 void loadPackage( 121 string dir, 122 string archType, 123 string buildType, 124 string configName, 125 ref string compiler) 126 { 127 import std.algorithm, std..string, std.array, std.path; 128 setLogLevel(LogLevel.error); 129 auto absDir = dir.absolutePath.buildNormalizedPath; 130 auto dub = new Dub(absDir); 131 if (compiler.length == 0) 132 compiler = dub.defaultCompiler; 133 if (archType.length == 0) 134 archType = dub.defaultArchitecture; 135 dub.loadPackage(); 136 loadPackage(dub, null, 137 archType, 138 buildType, 139 configName, 140 compiler); 141 } 142 143 @system unittest 144 { 145 import std.file; 146 if ("testcases/case002".exists) 147 { 148 PackageConfig cfg; 149 string compiler; 150 cfg.loadPackage("testcases/case002", "x86_64", "debug", null, compiler); 151 } 152 } 153 154 @system unittest 155 { 156 import std.file; 157 import std.path; 158 import std.algorithm; 159 import std.array; 160 if ("testcases/issue32".exists) 161 { 162 PackageConfig cfg; 163 string compiler; 164 cfg.loadPackage("testcases/issue32", "x86_64", "debug", "valid", compiler); 165 assert(cfg.files.length == 1); 166 assert(cfg.subPackages.length == 1); 167 assert(cfg.subPackages[0].name == "issue32:subpkg"); 168 assert(cfg.subPackages[0].files.length == 2); 169 assert(cfg.subPackages[0].files.map!(a => a.baseName).array.sort().equal(["foo.d", "lib.d"])); 170 171 cfg = PackageConfig.init; 172 cfg.loadPackage("testcases/issue32", "x86_64", "debug", null, compiler); 173 assert(cfg.files.length == 1); 174 assert(cfg.subPackages.length == 1); 175 assert(cfg.subPackages[0].name == "issue32:subpkg"); 176 assert(cfg.subPackages[0].files.length == 2); 177 assert(cfg.subPackages[0].files.map!(a => a.baseName).array.sort().equal(["bar.d", "lib.d"])); 178 } 179 } 180 } 181 182 private string _getHomeDirectory() 183 { 184 import std.file, std.path; 185 version (Posix) 186 { 187 return expandTilde("~"); 188 } 189 else version (Windows) 190 { 191 import core.runtime; 192 import core.sys.windows.windows, core.sys.windows.shlobj; 193 wchar[MAX_PATH+1] dst; 194 auto mod = LoadLibraryW("Shell32.dll"); 195 if (!mod) 196 return getcwd; 197 198 alias SHGetFolderPathProc = extern (Windows) HRESULT function(HWND, int, HANDLE, DWORD, LPWSTR); 199 auto getFolderPath = cast(SHGetFolderPathProc)GetProcAddress(cast(HMODULE)mod, "SHGetFolderPathW"); 200 if (getFolderPath is null) 201 return getcwd; 202 if (getFolderPath(null, CSIDL_PROFILE, null, 0, dst.ptr) == S_OK) 203 { 204 import std.algorithm, std.conv; 205 auto idx = dst.ptr.lstrlen(); 206 if (idx < dst.length) 207 return dst[0..idx].to!string(); 208 } 209 return getcwd; 210 } 211 } 212 213 /******************************************************************************* 214 * Gendoc's configuration data structure 215 */ 216 struct GendocConfig 217 { 218 /// 219 @optional 220 string[] ddocs; 221 /// 222 @optional 223 string[] sourceDocs; 224 /// 225 @optional 226 string target; 227 /// 228 @optional 229 string[] excludePaths; 230 /// 231 @optional 232 string[] excludePatterns = [ 233 "(?:(?<=/)|^)\\.[^/.]+$", 234 "(?:(?<=[^/]+/)|^)_[^/]+$", 235 "(?:(?<=[^/]+/)|^)internal(?:\\.d)?$"]; 236 /// 237 @optional 238 string[] excludePackages; 239 /// {"keyName": ["dub_package_name_regex_pattern1", "dub_package_name_regex_pattern2"]} 240 @optional 241 string[][string] combinedDubPackagePatterns; 242 /// 243 @optional 244 string[] excludePackagePatterns = [ 245 "(?:(?<=[^:]+/)|^)_[^/]+$", 246 ":docs?$"]; 247 248 /// 249 @optional 250 bool enableGenerateJSON = true; 251 252 /// 253 void fixPath(string dirPath) 254 { 255 import std.algorithm, std.path, std.file, std.process; 256 import gendoc.misc; 257 ddocs = ddocs.remove!(a => a.length == 0); 258 sourceDocs = sourceDocs.remove!(a => a.length == 0); 259 auto map = [ 260 "GENDOC_DIR": thisExePath.dirName.absolutePath, 261 "GENDOC_SD_DIR": thisExePath.dirName.buildPath("source_docs").exists 262 ? thisExePath.dirName.buildPath("source_docs") 263 : thisExePath.dirName.buildPath("../etc/.gendoc/docs").exists 264 ? thisExePath.dirName.buildNormalizedPath("../etc/.gendoc/docs") 265 : thisExePath.dirName.absolutePath, 266 "GENDOC_DD_DIR": thisExePath.dirName.buildPath("ddoc").exists 267 ? thisExePath.dirName.buildPath("ddoc") 268 : thisExePath.dirName.buildPath("../etc/.gendoc/ddoc").exists 269 ? thisExePath.dirName.buildNormalizedPath("../etc/.gendoc/ddoc") 270 : thisExePath.dirName.absolutePath, 271 "PROJECT_DIR": dirPath.absolutePath, 272 "WORK_DIR": getcwd.absolutePath]; 273 bool mapFunc(ref string arg, MacroType type) 274 { 275 if (auto val = map.get(arg, null)) 276 { 277 arg = val; 278 return true; 279 } 280 if (auto val = environment.get(arg, null)) 281 { 282 arg = val; 283 return true; 284 } 285 return false; 286 } 287 foreach (ref d; ddocs) 288 d = d.expandMacro(&mapFunc); 289 foreach (ref d; sourceDocs) 290 d = d.expandMacro(&mapFunc); 291 target = target.expandMacro(&mapFunc); 292 293 foreach (ref d; ddocs) 294 { 295 if (!d.isAbsolute) 296 d = buildPath(dirPath, d); 297 } 298 foreach (ref d; sourceDocs) 299 { 300 if (!d.isAbsolute) 301 d = buildPath(dirPath, d); 302 } 303 if (target.length > 0 && !target.isAbsolute) 304 target = buildPath(dirPath, target); 305 } 306 307 /// 308 private bool _loadConfigFromFile(string p) 309 { 310 debug import std.stdio; 311 import dub.internal.utils; 312 import std.file, std.path; 313 if (!p.exists || !p.isFile) 314 return false; 315 debug writeln("Configuration loaded from: " ~ p); 316 this.deserializeJson!GendocConfig(jsonFromFile(NativePath(p))); 317 fixPath(p.dirName); 318 return true; 319 } 320 321 /// 322 private bool _isExistsDir(string p) 323 { 324 import std.file, std.path; 325 return p.exists && p.isDir; 326 } 327 328 /// 329 private bool _loadConfigFromDir(string p, bool enableRawDocs) 330 { 331 debug import std.stdio; 332 import std.file, std.path; 333 auto ddocDir = p.buildPath("ddoc"); 334 if (!_isExistsDir(ddocDir)) 335 return false; 336 string sourceDocsDir; 337 if (enableRawDocs) 338 { 339 sourceDocsDir = p.buildPath("docs"); 340 if (_isExistsDir(sourceDocsDir)) 341 { 342 ddocs = [ddocDir]; 343 sourceDocs = [sourceDocsDir]; 344 debug writeln("Configuration loaded from: " ~ p); 345 return true; 346 } 347 } 348 sourceDocsDir = p.buildPath("source_docs"); 349 if (_isExistsDir(sourceDocsDir)) 350 { 351 ddocs = [ddocDir]; 352 sourceDocs = [sourceDocsDir]; 353 debug writeln("Configuration loaded from: " ~ p); 354 return true; 355 } 356 return false; 357 } 358 359 /*************************************************************************** 360 * Load configuration from specifiered path 361 * 362 */ 363 bool loadConfig(string path) 364 { 365 import std.file, std.path; 366 // 1.1. (--gendocConfig=<jsonfile>) 367 if (_loadConfigFromFile(path)) 368 return true; 369 370 // 1.2. (--gendocConfig=<directory>)/settings.json 371 if (_loadConfigFromFile(path.buildPath("settings.json"))) 372 return true; 373 374 // 1.3. (--gendocConfig=<directory>)/gendoc.json 375 if (_loadConfigFromFile(path.buildPath("gendoc.json"))) 376 return true; 377 378 // 1.4. (--gendocConfig=<directory>)/ddoc and (--gendocConfig=<directory>)/docs 379 // 1.5. (--gendocConfig=<directory>)/ddoc and (--gendocConfig=<directory>)/source_docs 380 if (_loadConfigFromDir(path, true)) 381 return true; 382 383 return false; 384 } 385 386 /// ditto 387 bool loadDefaultConfig(string root) 388 { 389 import std.file, std.path; 390 // 2.1. ./.gendoc.json 391 if (_loadConfigFromFile(root.buildPath(".gendoc.json"))) 392 return true; 393 394 // 2.2. ./gendoc.json 395 if (_loadConfigFromFile(root.buildPath("gendoc.json"))) 396 return true; 397 398 // 2.3. ./.gendoc/settings.json 399 if (_loadConfigFromFile(root.buildPath(".gendoc/settings.json"))) 400 return true; 401 402 // 2.4. ./.gendoc/gendoc.json 403 if (_loadConfigFromFile(root.buildPath(".gendoc/gendoc.json"))) 404 return true; 405 406 // 2.5. ./.gendoc/ddoc and ./.gendoc/docs 407 // 2.6. ./.gendoc/ddoc and ./.gendoc/source_docs 408 if (_loadConfigFromDir(root.buildPath(".gendoc"), true)) 409 return true; 410 411 // 2.7. ./ddoc and ./source_docs 412 // (docs may be a target) 413 if (_loadConfigFromDir(root, false)) 414 return true; 415 416 auto homeDir = _getHomeDirectory(); 417 418 419 // 3.1. $(HOME)/.gendoc.json 420 if (_loadConfigFromFile(homeDir.buildPath(".gendoc.json"))) 421 return true; 422 423 // 3.2. $(HOME)/gendoc.json 424 if (_loadConfigFromFile(homeDir.buildPath("gendoc.json"))) 425 return true; 426 427 // 3.3. $(HOME)/.gendoc/settings.json 428 if (_loadConfigFromFile(homeDir.buildPath(".gendoc/settings.json"))) 429 return true; 430 431 // 3.4. $(HOME)/.gendoc/gendoc.json 432 if (_loadConfigFromFile(homeDir.buildPath(".gendoc/gendoc.json"))) 433 return true; 434 435 // 3.5. $(HOME)/.gendoc/ddoc and $(HOME)/.gendoc/docs 436 // 3.6. $(HOME)/.gendoc/ddoc and $(HOME)/.gendoc/sourcec_docs 437 if (_loadConfigFromDir(homeDir.buildPath(".gendoc"), true)) 438 return true; 439 440 auto gendocExeDir = thisExePath.dirName; 441 442 // 4.1. (gendocExeDir)/gendoc.json 443 if (_loadConfigFromFile(gendocExeDir.buildPath(".gendoc.json"))) 444 return true; 445 446 // 4.2. (gendocExeDir)/gendoc.json 447 if (_loadConfigFromFile(gendocExeDir.buildPath("gendoc.json"))) 448 return true; 449 450 // 4.3. (gendocExeDir)/.gendoc/settings.json 451 if (_loadConfigFromFile(gendocExeDir.buildPath(".gendoc/settings.json"))) 452 return true; 453 454 // 4.4. (gendocExeDir)/.gendoc/gendoc.json 455 if (_loadConfigFromFile(gendocExeDir.buildPath(".gendoc/gendoc.json"))) 456 return true; 457 458 // 4.5. (gendocExeDir)/.gendoc/ddoc and (gendocExeDir)/.gendoc/docs 459 // 4.6. (gendocExeDir)/.gendoc/ddoc and (gendocExeDir)/.gendoc/source_docs 460 if (_loadConfigFromDir(gendocExeDir.buildPath(".gendoc"), true)) 461 return true; 462 463 // 4.7. (gendocExeDir)/ddoc and (gendocExeDir)/source_docs 464 // (docs may be a gendoc's document target) 465 if (_loadConfigFromDir(gendocExeDir, false)) 466 return true; 467 468 auto gendocEtcDir = gendocExeDir.dirName.buildPath("etc"); 469 470 // 5.1. (gendocEtcDir)/gendoc.json 471 if (_loadConfigFromFile(gendocEtcDir.buildPath(".gendoc.json"))) 472 return true; 473 474 // 5.2. (gendocEtcDir)/gendoc.json 475 if (_loadConfigFromFile(gendocEtcDir.buildPath("gendoc.json"))) 476 return true; 477 478 // 5.3. (gendocEtcDir)/.gendoc/settings.json 479 if (_loadConfigFromFile(gendocEtcDir.buildPath(".gendoc/settings.json"))) 480 return true; 481 482 // 5.4. (gendocEtcDir)/.gendoc/gendoc.json 483 if (_loadConfigFromFile(gendocEtcDir.buildPath(".gendoc/gendoc.json"))) 484 return true; 485 486 // 5.5. (gendocEtcDir)/.gendoc/ddoc and (gendocEtcDir)/.gendoc/docs 487 // 5.6. (gendocEtcDir)/.gendoc/ddoc and (gendocEtcDir)/.gendoc/source_docs 488 if (_loadConfigFromDir(gendocEtcDir.buildPath(".gendoc"), true)) 489 return true; 490 491 return false; 492 } 493 494 /// 495 void setup(string root, string configFile, string[] optDdocs, string[] optSourceDocs, string optTarget) 496 { 497 import std.algorithm, std.array, std.path, std.file, std.exception; 498 if (configFile.length > 0) 499 { 500 // 1.コマンドライン引数によってファイルの指定があった場合 501 auto filepath = root.buildPath(configFile); 502 // コマンドラインの指定があるのに構成が見つからない場合はエラー 503 loadConfig(filepath).enforce("Cannot load configuration: " ~ filepath); 504 } 505 else 506 { 507 // 2.コマンドライン引数がない場合はデフォルトから読み込み 508 loadDefaultConfig(root); 509 } 510 511 if (optDdocs.length > 0) 512 ddocs = optDdocs.map!( 513 a => a.isAbsolute ? a : root.buildPath(a)).array; 514 if (optSourceDocs.length > 0) 515 sourceDocs = optSourceDocs.map!( 516 a => a.isAbsolute ? a : root.buildPath(a)).array; 517 if (optTarget.length > 0) 518 target = optTarget.isAbsolute ? optTarget : root.buildPath(optTarget); 519 520 // default settings 521 if (ddocs.length == 0 && root.buildPath("ddoc").exists) 522 ddocs = [root.buildPath("ddoc")]; 523 if (ddocs.length == 0 && thisExePath.dirName.buildPath("ddoc").exists) 524 ddocs = [thisExePath.dirName.buildPath("ddoc")]; 525 if (sourceDocs.length == 0 && root.buildPath("source_docs").exists) 526 sourceDocs = [root.buildPath("source_docs")]; 527 if (sourceDocs.length == 0 && thisExePath.dirName.buildPath("source_docs").exists) 528 sourceDocs = [thisExePath.dirName.buildPath("source_docs")]; 529 if (target.length == 0) 530 target = root.buildPath("docs"); 531 532 // check 533 foreach (ref d; ddocs) 534 { 535 enforce(d.exists && d.isDir, "ddoc directory is missing: " ~ d); 536 enforce(filenameCmp(target.absolutePath.buildNormalizedPath, d.absolutePath.buildNormalizedPath) != 0, 537 "ddoc dir cannot be same to target: " ~ target); 538 } 539 foreach (ref d; sourceDocs) 540 { 541 enforce(d.exists && d.isDir, "source_docs directory is missing: " ~ d); 542 enforce(filenameCmp(target.absolutePath.buildNormalizedPath, d.absolutePath.buildNormalizedPath) != 0, 543 "source_docs dir cannot be same to target: " ~ target); 544 } 545 } 546 } 547 548 549 550 /******************************************************************************* 551 * 552 */ 553 struct Config 554 { 555 import std.getopt; 556 /// 557 string compiler; 558 /// 559 PackageConfig packageData; 560 /// 561 GendocConfig gendocData; 562 /// 563 bool singleFile; 564 /// 565 bool quiet; 566 /// 567 bool varbose; 568 /*************************************************************************** 569 * 570 */ 571 GetoptResult setup(string[] commandlineArgs) 572 { 573 import std.file, std.path; 574 575 string configFile; 576 Config tmp; 577 bool saveConfig; 578 string archType; 579 string buildType = "debug"; 580 string configName; 581 string root = "."; 582 583 string gendocConfig; 584 string[] ddocs; 585 string[] sourceDocs; 586 string target; 587 588 auto ret = commandlineArgs.getopt( 589 config.caseSensitive, 590 config.bundling, 591 "a|arch", "Archtecture of dub project.", &archType, 592 "b|build", "Build type of dub project.", &buildType, 593 "c|config", "Configuration of dub project.", &configName, 594 "compiler", "Specifies the compiler binary to use (can be a path).", &compiler, 595 "gendocDdocs", "Ddoc sources of document files.", &ddocs, 596 "gendocSourceDocs", "Source of document files.", &sourceDocs, 597 "gendocTarget", "Target directory of generated documents.", &target, 598 "gendocConfig", "Configuration file of gendoc.", &configFile, 599 "root", "Path to operate in instead of the current working dir.", &root, 600 "singleFile", "Single file generation mode.", &singleFile, 601 "v|varbose", "Display varbose messages.", &varbose, 602 "q|quiet", "Non-display messages.", &quiet 603 ); 604 605 packageData.loadPackage(root, archType, buildType, configName, compiler); 606 gendocData.setup(root, configFile, ddocs, sourceDocs, target); 607 608 return ret; 609 } 610 }