1 /** 2 Generator for project files 3 4 Copyright: © 2012-2013 Matthias Dondorff, © 2013-2016 Sönke Ludwig 5 License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file. 6 Authors: Matthias Dondorff 7 */ 8 module dub.generators.generator; 9 10 import dub.compilers.compiler; 11 import dub.generators.cmake; 12 import dub.generators.build; 13 import dub.generators.sublimetext; 14 import dub.generators.visuald; 15 import dub.internal.vibecompat.core.file; 16 import dub.internal.vibecompat.core.log; 17 import dub.internal.vibecompat.inet.path; 18 import dub.package_; 19 import dub.packagemanager; 20 import dub.project; 21 22 import std.algorithm : map, filter, canFind, balancedParens; 23 import std.array : array; 24 import std.array; 25 import std.exception; 26 import std.file; 27 import std.string; 28 29 30 /** 31 Common interface for project generators/builders. 32 */ 33 class ProjectGenerator 34 { 35 /** Information about a single binary target. 36 37 A binary target can either be an executable or a static/dynamic library. 38 It consists of one or more packages. 39 */ 40 struct TargetInfo { 41 /// The root package of this target 42 Package pack; 43 44 /// All packages compiled into this target 45 Package[] packages; 46 47 /// The configuration used for building the root package 48 string config; 49 50 /** Build settings used to build the target. 51 52 The build settings include all sources of all contained packages. 53 54 Depending on the specific generator implementation, it may be 55 necessary to add any static or dynamic libraries generated for 56 child targets ($(D linkDependencies)). 57 */ 58 BuildSettings buildSettings; 59 60 /** List of all dependencies. 61 62 This list includes dependencies that are not the root of a binary 63 target. 64 */ 65 string[] dependencies; 66 67 /** List of all binary dependencies. 68 69 This list includes all dependencies that are the root of a binary 70 target. 71 */ 72 string[] linkDependencies; 73 } 74 75 protected { 76 Project m_project; 77 } 78 79 this(Project project) 80 { 81 m_project = project; 82 } 83 84 /** Performs the full generator process. 85 */ 86 final void generate(GeneratorSettings settings) 87 { 88 import dub.compilers.utils : enforceBuildRequirements; 89 90 if (!settings.config.length) settings.config = m_project.getDefaultConfiguration(settings.platform); 91 92 string[string] configs = m_project.getPackageConfigs(settings.platform, settings.config); 93 TargetInfo[string] targets; 94 95 foreach (pack; m_project.getTopologicalPackageList(true, null, configs)) { 96 BuildSettings buildSettings; 97 auto config = configs[pack.name]; 98 buildSettings.processVars(m_project, pack, pack.getBuildSettings(settings.platform, config), true); 99 targets[pack.name] = TargetInfo(pack, [pack], config, buildSettings); 100 101 prepareGeneration(pack, m_project, settings, buildSettings); 102 } 103 104 string[] mainfiles = configurePackages(m_project.rootPackage, targets, settings); 105 106 addBuildTypeSettings(targets, settings); 107 foreach (ref t; targets.byValue) enforceBuildRequirements(t.buildSettings); 108 auto bs = &targets[m_project.rootPackage.name].buildSettings; 109 if (bs.targetType == TargetType.executable) bs.addSourceFiles(mainfiles); 110 111 generateTargets(settings, targets); 112 113 foreach (pack; m_project.getTopologicalPackageList(true, null, configs)) { 114 BuildSettings buildsettings; 115 buildsettings.processVars(m_project, pack, pack.getBuildSettings(settings.platform, configs[pack.name]), true); 116 bool generate_binary = !(buildsettings.options & BuildOption.syntaxOnly); 117 finalizeGeneration(pack, m_project, settings, buildsettings, NativePath(bs.targetPath), generate_binary); 118 } 119 120 performPostGenerateActions(settings, targets); 121 } 122 123 /** Overridden in derived classes to implement the actual generator functionality. 124 125 The function should go through all targets recursively. The first target 126 (which is guaranteed to be there) is 127 $(D targets[m_project.rootPackage.name]). The recursive descent is then 128 done using the $(D TargetInfo.linkDependencies) list. 129 130 This method is also potentially responsible for running the pre and post 131 build commands, while pre and post generate commands are already taken 132 care of by the $(D generate) method. 133 134 Params: 135 settings = The generator settings used for this run 136 targets = A map from package name to TargetInfo that contains all 137 binary targets to be built. 138 */ 139 protected abstract void generateTargets(GeneratorSettings settings, in TargetInfo[string] targets); 140 141 /** Overridable method to be invoked after the generator process has finished. 142 143 An examples of functionality placed here is to run the application that 144 has just been built. 145 */ 146 protected void performPostGenerateActions(GeneratorSettings settings, in TargetInfo[string] targets) {} 147 148 /** Configure `rootPackage` and all of it's dependencies. 149 150 1. Merge versions, debugVersions, and inheritable build 151 settings from dependents to their dependencies. 152 153 2. Define version identifiers Have_dependency_xyz for all 154 direct dependencies of all packages. 155 156 3. Merge versions, debugVersions, and inheritable build settings from 157 dependencies to their dependents, so that importer and importee are ABI 158 compatible. This also transports all Have_dependency_xyz version 159 identifiers to `rootPackage`. 160 161 Note: The upwards inheritance is done at last so that siblings do not 162 influence each other, also see https://github.com/dlang/dub/pull/1128. 163 164 Note: Targets without output are integrated into their 165 dependents and removed from `targets`. 166 */ 167 private string[] configurePackages(Package rootPackage, TargetInfo[string] targets, GeneratorSettings genSettings) 168 { 169 import std.algorithm : remove, sort; 170 import std.range : repeat; 171 172 // 0. do shallow configuration (not including dependencies) of all packages 173 TargetType determineTargetType(const ref TargetInfo ti) 174 { 175 TargetType tt = ti.buildSettings.targetType; 176 if (ti.pack is rootPackage) { 177 if (tt == TargetType.autodetect || tt == TargetType.library) tt = TargetType.staticLibrary; 178 } else { 179 if (tt == TargetType.autodetect || tt == TargetType.library) tt = genSettings.combined ? TargetType.sourceLibrary : TargetType.staticLibrary; 180 else if (tt == TargetType.dynamicLibrary) { 181 logWarn("Dynamic libraries are not yet supported as dependencies - building as static library."); 182 tt = TargetType.staticLibrary; 183 } 184 } 185 if (tt != TargetType.none && tt != TargetType.sourceLibrary && ti.buildSettings.sourceFiles.empty) { 186 logWarn(`Configuration '%s' of package %s contains no source files. Please add {"targetType": "none"} to its package description to avoid building it.`, 187 ti.config, ti.pack.name); 188 tt = TargetType.none; 189 } 190 return tt; 191 } 192 193 string[] mainSourceFiles; 194 bool[string] hasOutput; 195 196 foreach (ref ti; targets.byValue) 197 { 198 auto bs = &ti.buildSettings; 199 // determine the actual target type 200 bs.targetType = determineTargetType(ti); 201 202 switch (bs.targetType) 203 { 204 case TargetType.none: 205 // ignore any build settings for targetType none (only dependencies will be processed) 206 *bs = BuildSettings.init; 207 bs.targetType = TargetType.none; 208 break; 209 210 case TargetType.executable: 211 break; 212 213 case TargetType.dynamicLibrary: 214 // set -fPIC for dynamic library builds 215 ti.buildSettings.addOptions(BuildOption.pic); 216 goto default; 217 218 default: 219 // remove any mainSourceFile from non-executable builds 220 if (bs.mainSourceFile.length) { 221 bs.sourceFiles = bs.sourceFiles.remove!(f => f == bs.mainSourceFile); 222 mainSourceFiles ~= bs.mainSourceFile; 223 } 224 break; 225 } 226 bool generatesBinary = bs.targetType != TargetType.sourceLibrary && bs.targetType != TargetType.none; 227 hasOutput[ti.pack.name] = generatesBinary || ti.pack is rootPackage; 228 } 229 230 // mark packages as visited (only used during upwards propagation) 231 void[0][Package] visited; 232 233 // collect all dependencies 234 void collectDependencies(Package pack, ref TargetInfo ti, TargetInfo[string] targets, size_t level = 0) 235 { 236 // use `visited` here as pkgs cannot depend on themselves 237 if (pack in visited) 238 return; 239 // transitive dependencies must be visited multiple times, see #1350 240 immutable transitive = !hasOutput[pack.name]; 241 if (!transitive) 242 visited[pack] = typeof(visited[pack]).init; 243 244 auto bs = &ti.buildSettings; 245 if (hasOutput[pack.name]) 246 logDebug("%sConfiguring target %s (%s %s %s)", ' '.repeat(2 * level), pack.name, bs.targetType, bs.targetPath, bs.targetName); 247 else 248 logDebug("%sConfiguring target without output %s", ' '.repeat(2 * level), pack.name); 249 250 // get specified dependencies, e.g. vibe-d ~0.8.1 251 auto deps = pack.getDependencies(targets[pack.name].config); 252 logDebug("deps: %s -> %(%s, %)", pack.name, deps.byKey); 253 foreach (depname; deps.keys.sort()) 254 { 255 auto depspec = deps[depname]; 256 // get selected package for that dependency, e.g. vibe-d 0.8.2-beta.2 257 auto deppack = m_project.getDependency(depname, depspec.optional); 258 if (deppack is null) continue; // optional and not selected 259 260 // if dependency has no output 261 if (!hasOutput[depname]) { 262 // add itself 263 ti.packages ~= deppack; 264 // and it's transitive dependencies to current target 265 collectDependencies(deppack, ti, targets, level + 1); 266 continue; 267 } 268 auto depti = &targets[depname]; 269 const depbs = &depti.buildSettings; 270 if (depbs.targetType == TargetType.executable) 271 continue; 272 // add to (link) dependencies 273 ti.dependencies ~= depname; 274 ti.linkDependencies ~= depname; 275 276 // recurse 277 collectDependencies(deppack, *depti, targets, level + 1); 278 279 // also recursively add all link dependencies of static libraries 280 // preserve topological sorting of dependencies for correct link order 281 if (depbs.targetType == TargetType.staticLibrary) 282 ti.linkDependencies = ti.linkDependencies.filter!(d => !depti.linkDependencies.canFind(d)).array ~ depti.linkDependencies; 283 } 284 } 285 286 collectDependencies(rootPackage, targets[rootPackage.name], targets); 287 static if (__VERSION__ > 2070) 288 visited.clear(); 289 else 290 destroy(visited); 291 292 // 1. downwards inherits versions, debugVersions, and inheritable build settings 293 static void configureDependencies(in ref TargetInfo ti, TargetInfo[string] targets, size_t level = 0) 294 { 295 // do not use `visited` here as dependencies must inherit 296 // configurations from *all* of their parents 297 logDebug("%sConfigure dependencies of %s, deps:%(%s, %)", ' '.repeat(2 * level), ti.pack.name, ti.dependencies); 298 foreach (depname; ti.dependencies) 299 { 300 auto pti = &targets[depname]; 301 mergeFromDependent(ti.buildSettings, pti.buildSettings); 302 configureDependencies(*pti, targets, level + 1); 303 } 304 } 305 306 configureDependencies(targets[rootPackage.name], targets); 307 308 // 2. add Have_dependency_xyz for all direct dependencies of a target 309 // (includes incorporated non-target dependencies and their dependencies) 310 foreach (ref ti; targets.byValue) 311 { 312 import std.range : chain; 313 import dub.internal.utils : stripDlangSpecialChars; 314 315 auto bs = &ti.buildSettings; 316 auto pkgnames = ti.packages.map!(p => p.name).chain(ti.dependencies); 317 bs.addVersions(pkgnames.map!(pn => "Have_" ~ stripDlangSpecialChars(pn)).array); 318 } 319 320 // 3. upwards inherit full build configurations (import paths, versions, debugVersions, ...) 321 void configureDependents(ref TargetInfo ti, TargetInfo[string] targets, size_t level = 0) 322 { 323 // use `visited` here as pkgs cannot depend on themselves 324 if (ti.pack in visited) 325 return; 326 visited[ti.pack] = typeof(visited[ti.pack]).init; 327 328 logDiagnostic("%sConfiguring dependent %s, deps:%(%s, %)", ' '.repeat(2 * level), ti.pack.name, ti.dependencies); 329 // embedded non-binary dependencies 330 foreach (deppack; ti.packages[1 .. $]) 331 ti.buildSettings.add(targets[deppack.name].buildSettings); 332 // binary dependencies 333 foreach (depname; ti.dependencies) 334 { 335 auto pdepti = &targets[depname]; 336 configureDependents(*pdepti, targets, level + 1); 337 mergeFromDependency(pdepti.buildSettings, ti.buildSettings); 338 } 339 } 340 341 configureDependents(targets[rootPackage.name], targets); 342 static if (__VERSION__ > 2070) 343 visited.clear(); 344 else 345 destroy(visited); 346 347 // 4. override string import files in dependencies 348 static void overrideStringImports(ref TargetInfo ti, TargetInfo[string] targets, string[] overrides) 349 { 350 // do not use visited here as string imports can be overridden by *any* parent 351 // 352 // special support for overriding string imports in parent packages 353 // this is a candidate for deprecation, once an alternative approach 354 // has been found 355 if (ti.buildSettings.stringImportPaths.length) { 356 // override string import files (used for up to date checking) 357 foreach (ref f; ti.buildSettings.stringImportFiles) 358 { 359 foreach (o; overrides) 360 { 361 NativePath op; 362 if (f != o && NativePath(f).head == (op = NativePath(o)).head) { 363 logDebug("string import %s overridden by %s", f, o); 364 f = o; 365 ti.buildSettings.prependStringImportPaths(op.parentPath.toNativeString); 366 } 367 } 368 } 369 } 370 // add to overrides for recursion 371 overrides ~= ti.buildSettings.stringImportFiles; 372 // override dependencies 373 foreach (depname; ti.dependencies) 374 overrideStringImports(targets[depname], targets, overrides); 375 } 376 377 overrideStringImports(targets[rootPackage.name], targets, null); 378 379 // remove targets without output 380 foreach (name; targets.keys) 381 { 382 if (!hasOutput[name]) 383 targets.remove(name); 384 } 385 386 return mainSourceFiles; 387 } 388 389 private static void mergeFromDependent(in ref BuildSettings parent, ref BuildSettings child) 390 { 391 child.addVersions(parent.versions); 392 child.addDebugVersions(parent.debugVersions); 393 child.addOptions(BuildOptions(cast(BuildOptions)parent.options & inheritedBuildOptions)); 394 } 395 396 private static void mergeFromDependency(in ref BuildSettings child, ref BuildSettings parent) 397 { 398 import dub.compilers.utils : isLinkerFile; 399 400 parent.addDFlags(child.dflags); 401 parent.addVersions(child.versions); 402 parent.addDebugVersions(child.debugVersions); 403 parent.addImportPaths(child.importPaths); 404 parent.addStringImportPaths(child.stringImportPaths); 405 // linking of static libraries is done by parent 406 if (child.targetType == TargetType.staticLibrary) { 407 parent.addLinkerFiles(child.sourceFiles.filter!isLinkerFile.array); 408 parent.addLibs(child.libs); 409 parent.addLFlags(child.lflags); 410 } 411 } 412 413 // configure targets for build types such as release, or unittest-cov 414 private void addBuildTypeSettings(TargetInfo[string] targets, GeneratorSettings settings) 415 { 416 foreach (ref ti; targets.byValue) { 417 ti.buildSettings.add(settings.buildSettings); 418 419 // add build type settings and convert plain DFLAGS to build options 420 m_project.addBuildTypeSettings(ti.buildSettings, settings.platform, settings.buildType, ti.pack is m_project.rootPackage); 421 settings.compiler.extractBuildOptions(ti.buildSettings); 422 423 auto tt = ti.buildSettings.targetType; 424 bool generatesBinary = tt != TargetType.sourceLibrary && tt != TargetType.none; 425 enforce (generatesBinary || ti.pack !is m_project.rootPackage || (ti.buildSettings.options & BuildOption.syntaxOnly), 426 format("Main package must have a binary target type, not %s. Cannot build.", tt)); 427 } 428 } 429 } 430 431 432 struct GeneratorSettings { 433 BuildPlatform platform; 434 Compiler compiler; 435 string config; 436 string buildType; 437 BuildSettings buildSettings; 438 BuildMode buildMode = BuildMode.separate; 439 440 bool combined; // compile all in one go instead of each dependency separately 441 442 // only used for generator "build" 443 bool run, force, direct, rdmd, tempBuild, parallelBuild; 444 string[] runArgs; 445 void delegate(int status, string output) compileCallback; 446 void delegate(int status, string output) linkCallback; 447 void delegate(int status, string output) runCallback; 448 } 449 450 451 /** 452 Determines the mode in which the compiler and linker are invoked. 453 */ 454 enum BuildMode { 455 separate, /// Compile and link separately 456 allAtOnce, /// Perform compile and link with a single compiler invocation 457 singleFile, /// Compile each file separately 458 //multipleObjects, /// Generate an object file per module 459 //multipleObjectsPerModule, /// Use the -multiobj switch to generate multiple object files per module 460 //compileOnly /// Do not invoke the linker (can be done using a post build command) 461 } 462 463 464 /** 465 Creates a project generator of the given type for the specified project. 466 */ 467 ProjectGenerator createProjectGenerator(string generator_type, Project project) 468 { 469 assert(project !is null, "Project instance needed to create a generator."); 470 471 generator_type = generator_type.toLower(); 472 switch(generator_type) { 473 default: 474 throw new Exception("Unknown project generator: "~generator_type); 475 case "build": 476 logDebug("Creating build generator."); 477 return new BuildGenerator(project); 478 case "mono-d": 479 throw new Exception("The Mono-D generator has been removed. Use Mono-D's built in DUB support instead."); 480 case "visuald": 481 logDebug("Creating VisualD generator."); 482 return new VisualDGenerator(project); 483 case "sublimetext": 484 logDebug("Creating SublimeText generator."); 485 return new SublimeTextGenerator(project); 486 case "cmake": 487 logDebug("Creating CMake generator."); 488 return new CMakeGenerator(project); 489 } 490 } 491 492 493 /** 494 Runs pre-build commands and performs other required setup before project files are generated. 495 */ 496 private void prepareGeneration(in Package pack, in Project proj, in GeneratorSettings settings, 497 in BuildSettings buildsettings) 498 { 499 if (buildsettings.preGenerateCommands.length && !isRecursiveInvocation(pack.name)) { 500 logInfo("Running pre-generate commands for %s...", pack.name); 501 runBuildCommands(buildsettings.preGenerateCommands, pack, proj, settings, buildsettings); 502 } 503 } 504 505 /** 506 Runs post-build commands and copies required files to the binary directory. 507 */ 508 private void finalizeGeneration(in Package pack, in Project proj, in GeneratorSettings settings, 509 in BuildSettings buildsettings, NativePath target_path, bool generate_binary) 510 { 511 import std.path : globMatch; 512 513 if (buildsettings.postGenerateCommands.length && !isRecursiveInvocation(pack.name)) { 514 logInfo("Running post-generate commands for %s...", pack.name); 515 runBuildCommands(buildsettings.postGenerateCommands, pack, proj, settings, buildsettings); 516 } 517 518 if (generate_binary) { 519 if (!exists(buildsettings.targetPath)) 520 mkdirRecurse(buildsettings.targetPath); 521 522 if (buildsettings.copyFiles.length) { 523 void copyFolderRec(NativePath folder, NativePath dstfolder) 524 { 525 mkdirRecurse(dstfolder.toNativeString()); 526 foreach (de; iterateDirectory(folder.toNativeString())) { 527 if (de.isDirectory) { 528 copyFolderRec(folder ~ de.name, dstfolder ~ de.name); 529 } else { 530 try hardLinkFile(folder ~ de.name, dstfolder ~ de.name, true); 531 catch (Exception e) { 532 logWarn("Failed to copy file %s: %s", (folder ~ de.name).toNativeString(), e.msg); 533 } 534 } 535 } 536 } 537 538 void tryCopyDir(string file) 539 { 540 auto src = NativePath(file); 541 if (!src.absolute) src = pack.path ~ src; 542 auto dst = target_path ~ NativePath(file).head; 543 if (src == dst) { 544 logDiagnostic("Skipping copy of %s (same source and destination)", file); 545 return; 546 } 547 logDiagnostic(" %s to %s", src.toNativeString(), dst.toNativeString()); 548 try { 549 copyFolderRec(src, dst); 550 } catch(Exception e) logWarn("Failed to copy %s to %s: %s", src.toNativeString(), dst.toNativeString(), e.msg); 551 } 552 553 void tryCopyFile(string file) 554 { 555 auto src = NativePath(file); 556 if (!src.absolute) src = pack.path ~ src; 557 auto dst = target_path ~ NativePath(file).head; 558 if (src == dst) { 559 logDiagnostic("Skipping copy of %s (same source and destination)", file); 560 return; 561 } 562 logDiagnostic(" %s to %s", src.toNativeString(), dst.toNativeString()); 563 try { 564 hardLinkFile(src, dst, true); 565 } catch(Exception e) logWarn("Failed to copy %s to %s: %s", src.toNativeString(), dst.toNativeString(), e.msg); 566 } 567 logInfo("Copying files for %s...", pack.name); 568 string[] globs; 569 foreach (f; buildsettings.copyFiles) 570 { 571 if (f.canFind("*", "?") || 572 (f.canFind("{") && f.balancedParens('{', '}')) || 573 (f.canFind("[") && f.balancedParens('[', ']'))) 574 { 575 globs ~= f; 576 } 577 else 578 { 579 if (f.isDir) 580 tryCopyDir(f); 581 else 582 tryCopyFile(f); 583 } 584 } 585 if (globs.length) // Search all files for glob matches 586 { 587 foreach (f; dirEntries(pack.path.toNativeString(), SpanMode.breadth)) 588 { 589 foreach (glob; globs) 590 { 591 if (f.name().globMatch(glob)) 592 { 593 if (f.isDir) 594 tryCopyDir(f); 595 else 596 tryCopyFile(f); 597 break; 598 } 599 } 600 } 601 } 602 } 603 604 } 605 } 606 607 608 /** Runs a list of build commands for a particular package. 609 610 This function sets all DUB speficic environment variables and makes sure 611 that recursive dub invocations are detected and don't result in infinite 612 command execution loops. The latter could otherwise happen when a command 613 runs "dub describe" or similar functionality. 614 */ 615 void runBuildCommands(in string[] commands, in Package pack, in Project proj, 616 in GeneratorSettings settings, in BuildSettings build_settings) 617 { 618 import std.conv; 619 import std.process; 620 import dub.internal.utils; 621 622 string[string] env = environment.toAA(); 623 // TODO: do more elaborate things here 624 // TODO: escape/quote individual items appropriately 625 env["DFLAGS"] = join(cast(string[])build_settings.dflags, " "); 626 env["LFLAGS"] = join(cast(string[])build_settings.lflags," "); 627 env["VERSIONS"] = join(cast(string[])build_settings.versions," "); 628 env["LIBS"] = join(cast(string[])build_settings.libs," "); 629 env["IMPORT_PATHS"] = join(cast(string[])build_settings.importPaths," "); 630 env["STRING_IMPORT_PATHS"] = join(cast(string[])build_settings.stringImportPaths," "); 631 632 env["DC"] = settings.platform.compilerBinary; 633 env["DC_BASE"] = settings.platform.compiler; 634 env["D_FRONTEND_VER"] = to!string(settings.platform.frontendVersion); 635 636 env["DUB_PLATFORM"] = join(cast(string[])settings.platform.platform," "); 637 env["DUB_ARCH"] = join(cast(string[])settings.platform.architecture," "); 638 639 env["DUB_TARGET_TYPE"] = to!string(build_settings.targetType); 640 env["DUB_TARGET_PATH"] = build_settings.targetPath; 641 env["DUB_TARGET_NAME"] = build_settings.targetName; 642 env["DUB_WORKING_DIRECTORY"] = build_settings.workingDirectory; 643 env["DUB_MAIN_SOURCE_FILE"] = build_settings.mainSourceFile; 644 645 env["DUB_CONFIG"] = settings.config; 646 env["DUB_BUILD_TYPE"] = settings.buildType; 647 env["DUB_BUILD_MODE"] = to!string(settings.buildMode); 648 env["DUB_PACKAGE"] = pack.name; 649 env["DUB_PACKAGE_DIR"] = pack.path.toNativeString(); 650 env["DUB_ROOT_PACKAGE"] = proj.rootPackage.name; 651 env["DUB_ROOT_PACKAGE_DIR"] = proj.rootPackage.path.toNativeString(); 652 653 env["DUB_COMBINED"] = settings.combined? "TRUE" : ""; 654 env["DUB_RUN"] = settings.run? "TRUE" : ""; 655 env["DUB_FORCE"] = settings.force? "TRUE" : ""; 656 env["DUB_DIRECT"] = settings.direct? "TRUE" : ""; 657 env["DUB_RDMD"] = settings.rdmd? "TRUE" : ""; 658 env["DUB_TEMP_BUILD"] = settings.tempBuild? "TRUE" : ""; 659 env["DUB_PARALLEL_BUILD"] = settings.parallelBuild? "TRUE" : ""; 660 661 env["DUB_RUN_ARGS"] = (cast(string[])settings.runArgs).map!(escapeShellFileName).join(" "); 662 663 auto depNames = proj.dependencies.map!((a) => a.name).array(); 664 storeRecursiveInvokations(env, proj.rootPackage.name ~ depNames); 665 runCommands(commands, env); 666 } 667 668 private bool isRecursiveInvocation(string pack) 669 { 670 import std.algorithm : canFind, splitter; 671 import std.process : environment; 672 673 return environment 674 .get("DUB_PACKAGES_USED", "") 675 .splitter(",") 676 .canFind(pack); 677 } 678 679 private void storeRecursiveInvokations(string[string] env, string[] packs) 680 { 681 import std.algorithm : canFind, splitter; 682 import std.range : chain; 683 import std.process : environment; 684 685 env["DUB_PACKAGES_USED"] = environment 686 .get("DUB_PACKAGES_USED", "") 687 .splitter(",") 688 .chain(packs) 689 .join(","); 690 }