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.utils; 16 import dub.internal.vibecompat.core.file; 17 import dub.internal.vibecompat.data.json; 18 import dub.internal.vibecompat.inet.path; 19 import dub.internal.logging; 20 import dub.package_; 21 import dub.packagemanager; 22 import dub.project; 23 24 import std.algorithm : map, filter, canFind, balancedParens; 25 import std.array : array, appender, join; 26 import std.exception; 27 import std.file; 28 import std.string; 29 30 31 /** 32 Common interface for project generators/builders. 33 */ 34 class ProjectGenerator 35 { 36 /** Information about a single binary target. 37 38 A binary target can either be an executable or a static/dynamic library. 39 It consists of one or more packages. 40 */ 41 struct TargetInfo { 42 /// The root package of this target 43 Package pack; 44 45 /// All packages compiled into this target 46 Package[] packages; 47 48 /// The configuration used for building the root package 49 string config; 50 51 /** Build settings used to build the target. 52 53 The build settings include all sources of all contained packages. 54 55 Depending on the specific generator implementation, it may be 56 necessary to add any static or dynamic libraries generated for 57 child targets ($(D linkDependencies)). 58 */ 59 BuildSettings buildSettings; 60 61 /** List of all dependencies. 62 63 This list includes dependencies that are not the root of a binary 64 target. 65 */ 66 string[] dependencies; 67 68 /** List of all binary dependencies. 69 70 This list includes all dependencies that are the root of a binary 71 target. 72 */ 73 string[] linkDependencies; 74 } 75 76 private struct EnvironmentVariables 77 { 78 string[string] environments; 79 string[string] buildEnvironments; 80 string[string] runEnvironments; 81 string[string] preGenerateEnvironments; 82 string[string] postGenerateEnvironments; 83 string[string] preBuildEnvironments; 84 string[string] postBuildEnvironments; 85 string[string] preRunEnvironments; 86 string[string] postRunEnvironments; 87 88 this(const scope ref BuildSettings bs) 89 { 90 update(bs); 91 } 92 93 void update(Envs)(const scope auto ref Envs envs) 94 { 95 import std.algorithm: each; 96 envs.environments.byKeyValue.each!(pair => environments[pair.key] = pair.value); 97 envs.buildEnvironments.byKeyValue.each!(pair => buildEnvironments[pair.key] = pair.value); 98 envs.runEnvironments.byKeyValue.each!(pair => runEnvironments[pair.key] = pair.value); 99 envs.preGenerateEnvironments.byKeyValue.each!(pair => preGenerateEnvironments[pair.key] = pair.value); 100 envs.postGenerateEnvironments.byKeyValue.each!(pair => postGenerateEnvironments[pair.key] = pair.value); 101 envs.preBuildEnvironments.byKeyValue.each!(pair => preBuildEnvironments[pair.key] = pair.value); 102 envs.postBuildEnvironments.byKeyValue.each!(pair => postBuildEnvironments[pair.key] = pair.value); 103 envs.preRunEnvironments.byKeyValue.each!(pair => preRunEnvironments[pair.key] = pair.value); 104 envs.postRunEnvironments.byKeyValue.each!(pair => postRunEnvironments[pair.key] = pair.value); 105 } 106 107 void updateBuildSettings(ref BuildSettings bs) 108 { 109 bs.updateEnvironments(environments); 110 bs.updateBuildEnvironments(buildEnvironments); 111 bs.updateRunEnvironments(runEnvironments); 112 bs.updatePreGenerateEnvironments(preGenerateEnvironments); 113 bs.updatePostGenerateEnvironments(postGenerateEnvironments); 114 bs.updatePreBuildEnvironments(preBuildEnvironments); 115 bs.updatePostBuildEnvironments(postBuildEnvironments); 116 bs.updatePreRunEnvironments(preRunEnvironments); 117 bs.updatePostRunEnvironments(postRunEnvironments); 118 } 119 } 120 121 protected { 122 Project m_project; 123 NativePath m_tempTargetExecutablePath; 124 } 125 126 this(Project project) 127 { 128 m_project = project; 129 } 130 131 /** Performs the full generator process. 132 */ 133 final void generate(GeneratorSettings settings) 134 { 135 import dub.compilers.utils : enforceBuildRequirements; 136 137 if (!settings.config.length) settings.config = m_project.getDefaultConfiguration(settings.platform); 138 139 string[string] configs = m_project.getPackageConfigs(settings.platform, settings.config); 140 TargetInfo[string] targets; 141 EnvironmentVariables[string] envs; 142 143 foreach (pack; m_project.getTopologicalPackageList(true, null, configs)) { 144 auto config = configs[pack.name]; 145 auto bs = pack.getBuildSettings(settings.platform, config); 146 targets[pack.name] = TargetInfo(pack, [pack], config, bs); 147 envs[pack.name] = EnvironmentVariables(bs); 148 } 149 foreach (pack; m_project.getTopologicalPackageList(false, null, configs)) { 150 auto ti = pack.name in targets; 151 auto parentEnvs = ti.pack.name in envs; 152 foreach (deppkgName, depInfo; pack.getDependencies(ti.config)) { 153 if (auto childEnvs = deppkgName in envs) { 154 childEnvs.update(ti.buildSettings); 155 parentEnvs.update(childEnvs); 156 } 157 } 158 } 159 BuildSettings makeBuildSettings(in Package pack, ref BuildSettings src) 160 { 161 BuildSettings bs; 162 if (settings.buildSettings.options & BuildOption.lowmem) bs.options |= BuildOption.lowmem; 163 BuildSettings srcbs = src.dup; 164 envs[pack.name].updateBuildSettings(srcbs); 165 bs.processVars(m_project, pack, srcbs, settings, true); 166 return bs; 167 } 168 169 foreach (pack; m_project.getTopologicalPackageList(true, null, configs)) { 170 BuildSettings bs = makeBuildSettings(pack, targets[pack.name].buildSettings); 171 prepareGeneration(pack, m_project, settings, bs); 172 173 // Regenerate buildSettings.sourceFiles 174 if (bs.preGenerateCommands.length) { 175 auto newSettings = pack.getBuildSettings(settings.platform, configs[pack.name]); 176 bs = makeBuildSettings(pack, newSettings); 177 } 178 targets[pack.name].buildSettings = bs; 179 } 180 configurePackages(m_project.rootPackage, targets, settings); 181 182 addBuildTypeSettings(targets, settings); 183 foreach (ref t; targets.byValue) enforceBuildRequirements(t.buildSettings); 184 185 generateTargets(settings, targets); 186 187 foreach (pack; m_project.getTopologicalPackageList(true, null, configs)) { 188 auto config = configs[pack.name]; 189 auto pkgbs = pack.getBuildSettings(settings.platform, config); 190 BuildSettings buildsettings = makeBuildSettings(pack, pkgbs); 191 bool generate_binary = !(buildsettings.options & BuildOption.syntaxOnly); 192 auto bs = &targets[m_project.rootPackage.name].buildSettings; 193 auto targetPath = !m_tempTargetExecutablePath.empty ? m_tempTargetExecutablePath : 194 !bs.targetPath.empty ? NativePath(bs.targetPath) : 195 NativePath(buildsettings.targetPath); 196 197 finalizeGeneration(pack, m_project, settings, buildsettings, targetPath, generate_binary); 198 } 199 200 performPostGenerateActions(settings, targets); 201 } 202 203 /** Overridden in derived classes to implement the actual generator functionality. 204 205 The function should go through all targets recursively. The first target 206 (which is guaranteed to be there) is 207 $(D targets[m_project.rootPackage.name]). The recursive descent is then 208 done using the $(D TargetInfo.linkDependencies) list. 209 210 This method is also potentially responsible for running the pre and post 211 build commands, while pre and post generate commands are already taken 212 care of by the $(D generate) method. 213 214 Params: 215 settings = The generator settings used for this run 216 targets = A map from package name to TargetInfo that contains all 217 binary targets to be built. 218 */ 219 protected abstract void generateTargets(GeneratorSettings settings, in TargetInfo[string] targets); 220 221 /** Overridable method to be invoked after the generator process has finished. 222 223 An examples of functionality placed here is to run the application that 224 has just been built. 225 */ 226 protected void performPostGenerateActions(GeneratorSettings settings, in TargetInfo[string] targets) {} 227 228 /** Configure `rootPackage` and all of it's dependencies. 229 230 1. Merge versions, debugVersions, and inheritable build 231 settings from dependents to their dependencies. 232 233 2. Define version identifiers Have_dependency_xyz for all 234 direct dependencies of all packages. 235 236 3. Merge versions, debugVersions, and inheritable build settings from 237 dependencies to their dependents, so that importer and importee are ABI 238 compatible. This also transports all Have_dependency_xyz version 239 identifiers to `rootPackage`. 240 241 4. Merge injectSourceFiles from dependencies into their dependents. 242 This is based upon binary images and will transcend direct relationships 243 including shared libraries. 244 245 5. Filter unused versions and debugVersions from all targets. The 246 filters have previously been upwards inherited (3. and 4.) so that versions 247 used in a dependency are also applied to all dependents. 248 249 Note: The upwards inheritance is done at last so that siblings do not 250 influence each other, also see https://github.com/dlang/dub/pull/1128. 251 252 Note: Targets without output are integrated into their 253 dependents and removed from `targets`. 254 */ 255 private void configurePackages(Package rootPackage, TargetInfo[string] targets, GeneratorSettings genSettings) 256 { 257 import std.algorithm : remove, sort; 258 import std.range : repeat; 259 260 auto roottarget = &targets[rootPackage.name]; 261 262 // Handle the destination directory being overridden. 263 auto cwd = genSettings.toolWorkingDirectory; 264 auto dst = genSettings.destinationDirectory; 265 if (!dst.empty) { 266 auto targetPath = NativePath(roottarget.buildSettings.targetPath); 267 auto workdirPath = NativePath(roottarget.buildSettings.workingDirectory); 268 auto relTargetPath = targetPath.absolute ? targetPath.relativeTo(cwd) : targetPath; 269 auto relWorkdirPath = workdirPath.absolute ? workdirPath.relativeTo(cwd) : workdirPath; 270 auto relDestPath = dst.absolute ? dst.relativeTo(cwd) : dst; 271 272 roottarget.buildSettings.targetPath = (relDestPath ~ relTargetPath).toString(); 273 roottarget.buildSettings.workingDirectory = (relDestPath ~ relWorkdirPath).toString(); 274 } 275 276 // 0. do shallow configuration (not including dependencies) of all packages 277 TargetType determineTargetType(const ref TargetInfo ti, const ref GeneratorSettings genSettings) 278 { 279 TargetType tt = ti.buildSettings.targetType; 280 if (ti.pack is rootPackage) { 281 if (tt == TargetType.autodetect || tt == TargetType.library) tt = TargetType.staticLibrary; 282 } else { 283 if (tt == TargetType.autodetect || tt == TargetType.library) tt = genSettings.combined ? TargetType.sourceLibrary : TargetType.staticLibrary; 284 else if (genSettings.platform.architecture.canFind("x86_omf") && tt == TargetType.dynamicLibrary) { 285 // Unfortunately we cannot remove this check for OMF targets, 286 // due to Optlink not producing shared libraries without a lot of user intervention. 287 // For other targets, say MSVC it'll do the right thing for the most part, 288 // export is still a problem as of this writing, which means static libraries cannot have linking to them removed. 289 // But that is for the most part up to the developer, to get it working correctly. 290 291 logWarn("Dynamic libraries are not yet supported as dependencies for Windows target OMF - building as static library."); 292 tt = TargetType.staticLibrary; 293 } 294 } 295 if (tt != TargetType.none && tt != TargetType.sourceLibrary && ti.buildSettings.sourceFiles.empty) { 296 logWarn(`Configuration [%s] of package %s contains no source files. Please add %s to its package description to avoid building it.`, 297 ti.config.color(Color.blue), ti.pack.name.color(Mode.bold), `{"targetType": "none"}`.color(Mode.bold)); 298 tt = TargetType.none; 299 } 300 return tt; 301 } 302 303 string[] mainSourceFiles; 304 bool[string] hasOutput; 305 306 foreach (ref ti; targets.byValue) 307 { 308 auto bs = &ti.buildSettings; 309 // determine the actual target type 310 bs.targetType = determineTargetType(ti, genSettings); 311 312 switch (bs.targetType) 313 { 314 case TargetType.none: 315 // ignore any build settings for targetType none (only dependencies will be processed) 316 *bs = BuildSettings.init; 317 bs.targetType = TargetType.none; 318 break; 319 320 case TargetType.executable: 321 break; 322 323 case TargetType.dynamicLibrary: 324 // set -fPIC for dynamic library builds 325 ti.buildSettings.addOptions(BuildOption.pic); 326 goto default; 327 328 default: 329 // remove any mainSourceFile from non-executable builds 330 if (bs.mainSourceFile.length) { 331 bs.sourceFiles = bs.sourceFiles.remove!(f => f == bs.mainSourceFile); 332 mainSourceFiles ~= bs.mainSourceFile; 333 } 334 break; 335 } 336 bool generatesBinary = bs.targetType != TargetType.sourceLibrary && bs.targetType != TargetType.none; 337 hasOutput[ti.pack.name] = generatesBinary || ti.pack is rootPackage; 338 } 339 340 // add main source files to root executable 341 { 342 auto bs = &roottarget.buildSettings; 343 if (bs.targetType == TargetType.executable || genSettings.single) bs.addSourceFiles(mainSourceFiles); 344 } 345 346 if (genSettings.filterVersions) 347 foreach (ref ti; targets.byValue) 348 inferVersionFilters(ti); 349 350 // mark packages as visited (only used during upwards propagation) 351 void[0][Package] visited; 352 353 // collect all dependencies 354 void collectDependencies(Package pack, ref TargetInfo ti, TargetInfo[string] targets, size_t level = 0) 355 { 356 // use `visited` here as pkgs cannot depend on themselves 357 if (pack in visited) 358 return; 359 // transitive dependencies must be visited multiple times, see #1350 360 immutable transitive = !hasOutput[pack.name]; 361 if (!transitive) 362 visited[pack] = typeof(visited[pack]).init; 363 364 auto bs = &ti.buildSettings; 365 if (hasOutput[pack.name]) 366 logDebug("%sConfiguring target %s (%s %s %s)", ' '.repeat(2 * level), pack.name, bs.targetType, bs.targetPath, bs.targetName); 367 else 368 logDebug("%sConfiguring target without output %s", ' '.repeat(2 * level), pack.name); 369 370 // get specified dependencies, e.g. vibe-d ~0.8.1 371 auto deps = pack.getDependencies(targets[pack.name].config); 372 logDebug("deps: %s -> %(%s, %)", pack.name, deps.byKey); 373 foreach (depname; deps.keys.sort()) 374 { 375 auto depspec = deps[depname]; 376 // get selected package for that dependency, e.g. vibe-d 0.8.2-beta.2 377 auto deppack = m_project.getDependency(depname, depspec.optional); 378 if (deppack is null) continue; // optional and not selected 379 380 // if dependency has no output 381 if (!hasOutput[depname]) { 382 // add itself 383 ti.packages ~= deppack; 384 // and it's transitive dependencies to current target 385 collectDependencies(deppack, ti, targets, level + 1); 386 continue; 387 } 388 auto depti = &targets[depname]; 389 const depbs = &depti.buildSettings; 390 if (depbs.targetType == TargetType.executable && ti.buildSettings.targetType != TargetType.none) 391 continue; 392 393 // add to (link) dependencies 394 ti.dependencies ~= depname; 395 ti.linkDependencies ~= depname; 396 397 // recurse 398 collectDependencies(deppack, *depti, targets, level + 1); 399 400 // also recursively add all link dependencies of static *and* dynamic libraries 401 // preserve topological sorting of dependencies for correct link order 402 if (depbs.targetType == TargetType.staticLibrary || depbs.targetType == TargetType.dynamicLibrary) 403 ti.linkDependencies = ti.linkDependencies.filter!(d => !depti.linkDependencies.canFind(d)).array ~ depti.linkDependencies; 404 } 405 406 enforce(!(ti.buildSettings.targetType == TargetType.none && ti.dependencies.empty), 407 "Package with target type \"none\" must have dependencies to build."); 408 } 409 410 collectDependencies(rootPackage, *roottarget, targets); 411 visited.clear(); 412 413 // 1. downwards inherits versions, debugVersions, and inheritable build settings 414 static void configureDependencies(const scope ref TargetInfo ti, TargetInfo[string] targets, size_t level = 0) 415 { 416 417 // do not use `visited` here as dependencies must inherit 418 // configurations from *all* of their parents 419 logDebug("%sConfigure dependencies of %s, deps:%(%s, %)", ' '.repeat(2 * level), ti.pack.name, ti.dependencies); 420 foreach (depname; ti.dependencies) 421 { 422 auto pti = &targets[depname]; 423 mergeFromDependent(ti.buildSettings, pti.buildSettings); 424 configureDependencies(*pti, targets, level + 1); 425 } 426 } 427 428 configureDependencies(*roottarget, targets); 429 430 // 2. add Have_dependency_xyz for all direct dependencies of a target 431 // (includes incorporated non-target dependencies and their dependencies) 432 foreach (ref ti; targets.byValue) 433 { 434 import std.range : chain; 435 import dub.internal.utils : stripDlangSpecialChars; 436 437 auto bs = &ti.buildSettings; 438 auto pkgnames = ti.packages.map!(p => p.name).chain(ti.dependencies); 439 bs.addVersions(pkgnames.map!(pn => "Have_" ~ stripDlangSpecialChars(pn)).array); 440 } 441 442 // 3. upwards inherit full build configurations (import paths, versions, debugVersions, versionFilters, importPaths, ...) 443 444 // We do a check for if any dependency uses final binary injection source files, 445 // otherwise can ignore that bit of workload entirely 446 bool skipFinalBinaryMerging = true; 447 448 void configureDependents(ref TargetInfo ti, TargetInfo[string] targets, size_t level = 0) 449 { 450 // use `visited` here as pkgs cannot depend on themselves 451 if (ti.pack in visited) 452 return; 453 visited[ti.pack] = typeof(visited[ti.pack]).init; 454 455 logDiagnostic("%sConfiguring dependent %s, deps:%(%s, %)", ' '.repeat(2 * level), ti.pack.name, ti.dependencies); 456 // embedded non-binary dependencies 457 foreach (deppack; ti.packages[1 .. $]) 458 ti.buildSettings.add(targets[deppack.name].buildSettings); 459 460 // binary dependencies 461 foreach (depname; ti.dependencies) 462 { 463 auto pdepti = &targets[depname]; 464 465 configureDependents(*pdepti, targets, level + 1); 466 mergeFromDependency(pdepti.buildSettings, ti.buildSettings, genSettings.platform); 467 468 if (!pdepti.buildSettings.injectSourceFiles.empty) 469 skipFinalBinaryMerging = false; 470 } 471 } 472 473 configureDependents(*roottarget, targets); 474 visited.clear(); 475 476 // 4. As an extension to configureDependents we need to copy any injectSourceFiles 477 // in our dependencies (ignoring targetType) 478 void configureDependentsFinalImages(ref TargetInfo ti, TargetInfo[string] targets, ref TargetInfo finalBinaryTarget, size_t level = 0) 479 { 480 // use `visited` here as pkgs cannot depend on themselves 481 if (ti.pack in visited) 482 return; 483 visited[ti.pack] = typeof(visited[ti.pack]).init; 484 485 logDiagnostic("%sConfiguring dependent %s, deps:%(%s, %) for injectSourceFiles", ' '.repeat(2 * level), ti.pack.name, ti.dependencies); 486 487 foreach (depname; ti.dependencies) 488 { 489 auto pdepti = &targets[depname]; 490 491 if (!pdepti.buildSettings.injectSourceFiles.empty) 492 finalBinaryTarget.buildSettings.addSourceFiles(pdepti.buildSettings.injectSourceFiles); 493 494 configureDependentsFinalImages(*pdepti, targets, finalBinaryTarget, level + 1); 495 } 496 } 497 498 if (!skipFinalBinaryMerging) 499 { 500 foreach (ref target; targets.byValue) 501 { 502 switch (target.buildSettings.targetType) 503 { 504 case TargetType.executable: 505 case TargetType.dynamicLibrary: 506 configureDependentsFinalImages(target, targets, target); 507 508 // We need to clear visited for each target that is executable dynamicLibrary 509 // due to this process needing to be recursive based upon the final binary targets. 510 visited.clear(); 511 break; 512 513 default: 514 break; 515 } 516 } 517 } 518 519 // 5. Filter applicable version and debug version identifiers 520 if (genSettings.filterVersions) 521 { 522 foreach (name, ref ti; targets) 523 { 524 import std.algorithm.sorting : partition; 525 526 auto bs = &ti.buildSettings; 527 528 auto filtered = bs.versions.partition!(v => bs.versionFilters.canFind(v)); 529 logDebug("Filtering out unused versions for %s: %s", name, filtered); 530 bs.versions = bs.versions[0 .. $ - filtered.length]; 531 532 filtered = bs.debugVersions.partition!(v => bs.debugVersionFilters.canFind(v)); 533 logDebug("Filtering out unused debug versions for %s: %s", name, filtered); 534 bs.debugVersions = bs.debugVersions[0 .. $ - filtered.length]; 535 } 536 } 537 538 // 6. override string import files in dependencies 539 static void overrideStringImports(ref TargetInfo target, 540 ref TargetInfo parent, TargetInfo[string] targets, string[] overrides) 541 { 542 // Since string import paths are inherited from dependencies in the 543 // inheritance step above (step 3), it is guaranteed that all 544 // following dependencies will not have string import paths either, 545 // so we can skip the recursion here 546 if (!target.buildSettings.stringImportPaths.length) 547 return; 548 549 // do not use visited here as string imports can be overridden by *any* parent 550 // 551 // special support for overriding string imports in parent packages 552 // this is a candidate for deprecation, once an alternative approach 553 // has been found 554 bool any_override = false; 555 556 // override string import files (used for up to date checking) 557 foreach (ref f; target.buildSettings.stringImportFiles) 558 { 559 foreach (o; overrides) 560 { 561 NativePath op; 562 if (f != o && NativePath(f).head == (op = NativePath(o)).head) { 563 logDebug("string import %s overridden by %s", f, o); 564 f = o; 565 any_override = true; 566 } 567 } 568 } 569 570 // override string import paths by prepending to the list, in 571 // case there is any overlapping file 572 if (any_override) 573 target.buildSettings.prependStringImportPaths(parent.buildSettings.stringImportPaths); 574 575 // add all files to overrides for recursion 576 overrides ~= target.buildSettings.stringImportFiles; 577 578 // recursively override all dependencies with the accumulated files/paths 579 foreach (depname; target.dependencies) 580 overrideStringImports(targets[depname], target, targets, overrides); 581 } 582 583 // push string import paths/files down to all direct and indirect 584 // dependencies, overriding their own 585 foreach (depname; roottarget.dependencies) 586 overrideStringImports(targets[depname], *roottarget, targets, 587 roottarget.buildSettings.stringImportFiles); 588 589 // 7. downwards inherits dependency build settings 590 static void applyForcedSettings(const scope ref TargetInfo ti, TargetInfo[string] targets, 591 BuildSettings[string] dependBS, size_t level = 0) 592 { 593 594 static void apply(const scope ref BuildSettings forced, ref BuildSettings child) { 595 child.addDFlags(forced.dflags); 596 } 597 598 // apply to all dependencies 599 foreach (depname; ti.dependencies) 600 { 601 BuildSettings forcedSettings; 602 auto pti = &targets[depname]; 603 604 // fetch the forced dependency build settings 605 if (auto matchedSettings = depname in dependBS) 606 forcedSettings = *matchedSettings; 607 else if (auto matchedSettings = "*" in dependBS) 608 forcedSettings = *matchedSettings; 609 610 apply(forcedSettings, pti.buildSettings); 611 612 // recursively apply forced settings to all dependencies of his dependency 613 applyForcedSettings(*pti, targets, ["*" : forcedSettings], level + 1); 614 } 615 } 616 617 // apply both top level and configuration level forced dependency build settings 618 void applyDependencyBuildSettings (const RecipeDependency[string] configured_dbs) 619 { 620 BuildSettings[string] dependencyBuildSettings; 621 foreach (key, value; configured_dbs) 622 { 623 BuildSettings buildSettings; 624 if (auto target = key in targets) 625 { 626 // get platform specific build settings and process dub variables (BuildSettingsTemplate => BuildSettings) 627 value.settings.getPlatformSettings(buildSettings, genSettings.platform, target.pack.path); 628 buildSettings.processVars(m_project, target.pack, buildSettings, genSettings, true); 629 dependencyBuildSettings[key] = buildSettings; 630 } 631 } 632 applyForcedSettings(*roottarget, targets, dependencyBuildSettings); 633 } 634 applyDependencyBuildSettings(rootPackage.recipe.buildSettings.dependencies); 635 applyDependencyBuildSettings(rootPackage.getBuildSettings(genSettings.config).dependencies); 636 637 // remove targets without output 638 foreach (name; targets.keys) 639 { 640 if (!hasOutput[name]) 641 targets.remove(name); 642 } 643 } 644 645 // infer applicable version identifiers 646 private static void inferVersionFilters(ref TargetInfo ti) 647 { 648 import std.algorithm.searching : any; 649 import std.file : timeLastModified; 650 import std.path : extension; 651 import std.range : chain; 652 import std.regex : ctRegex, matchAll; 653 import std.stdio : File; 654 import std.datetime : Clock, SysTime, UTC; 655 import dub.compilers.utils : isLinkerFile; 656 import dub.internal.vibecompat.data.json : Json, JSONException; 657 658 auto bs = &ti.buildSettings; 659 660 // only infer if neither version filters are specified explicitly 661 if (bs.versionFilters.length || bs.debugVersionFilters.length) 662 { 663 logDebug("Using specified versionFilters for %s: %s %s", ti.pack.name, 664 bs.versionFilters, bs.debugVersionFilters); 665 return; 666 } 667 668 // check all existing source files for version identifiers 669 static immutable dexts = [".d", ".di"]; 670 auto srcs = chain(bs.sourceFiles, bs.importFiles, bs.stringImportFiles) 671 .filter!(f => dexts.canFind(f.extension)).filter!exists; 672 // try to load cached filters first 673 const cacheFilePath = packageCache(NativePath(ti.buildSettings.targetPath), ti.pack) 674 ~ "metadata_cache.json"; 675 enum silent_fail = true; 676 auto cache = jsonFromFile(cacheFilePath, silent_fail); 677 try 678 { 679 auto cachedFilters = cache["versionFilters"]; 680 if (cachedFilters.type != Json.Type.undefined) 681 cachedFilters = cachedFilters[ti.config]; 682 if (cachedFilters.type != Json.Type.undefined) 683 { 684 immutable mtime = SysTime.fromISOExtString(cachedFilters["mtime"].get!string); 685 if (!srcs.any!(src => src.timeLastModified > mtime)) 686 { 687 auto versionFilters = cachedFilters["versions"][].map!(j => j.get!string).array; 688 auto debugVersionFilters = cachedFilters["debugVersions"][].map!(j => j.get!string).array; 689 logDebug("Using cached versionFilters for %s: %s %s", ti.pack.name, 690 versionFilters, debugVersionFilters); 691 bs.addVersionFilters(versionFilters); 692 bs.addDebugVersionFilters(debugVersionFilters); 693 return; 694 } 695 } 696 } 697 catch (JSONException e) 698 { 699 logWarn("Exception during loading invalid package cache %s.\n%s", 700 ti.pack.path ~ ".dub/metadata_cache.json", e); 701 } 702 703 // use ctRegex for performance reasons, only small compile time increase 704 enum verRE = ctRegex!`(?:^|\s)version\s*\(\s*([^\s]*?)\s*\)`; 705 enum debVerRE = ctRegex!`(?:^|\s)debug\s*\(\s*([^\s]*?)\s*\)`; 706 707 auto versionFilters = appender!(string[]); 708 auto debugVersionFilters = appender!(string[]); 709 710 foreach (file; srcs) 711 { 712 foreach (line; File(file).byLine) 713 { 714 foreach (m; line.matchAll(verRE)) 715 if (!versionFilters.data.canFind(m[1])) 716 versionFilters.put(m[1].idup); 717 foreach (m; line.matchAll(debVerRE)) 718 if (!debugVersionFilters.data.canFind(m[1])) 719 debugVersionFilters.put(m[1].idup); 720 } 721 } 722 logDebug("Using inferred versionFilters for %s: %s %s", ti.pack.name, 723 versionFilters.data, debugVersionFilters.data); 724 bs.addVersionFilters(versionFilters.data); 725 bs.addDebugVersionFilters(debugVersionFilters.data); 726 727 auto cachedFilters = cache["versionFilters"]; 728 if (cachedFilters.type == Json.Type.undefined) 729 cachedFilters = cache["versionFilters"] = [ti.config: Json.emptyObject]; 730 cachedFilters[ti.config] = [ 731 "mtime": Json(Clock.currTime(UTC()).toISOExtString), 732 "versions": Json(versionFilters.data.map!Json.array), 733 "debugVersions": Json(debugVersionFilters.data.map!Json.array), 734 ]; 735 enum create_if_missing = true; 736 if (isWritableDir(cacheFilePath.parentPath, create_if_missing)) 737 writeJsonFile(cacheFilePath, cache); 738 } 739 740 private static void mergeFromDependent(const scope ref BuildSettings parent, ref BuildSettings child) 741 { 742 child.addVersions(parent.versions); 743 child.addDebugVersions(parent.debugVersions); 744 child.addOptions(Flags!BuildOption(parent.options & inheritedBuildOptions)); 745 } 746 747 private static void mergeFromDependency(const scope ref BuildSettings child, ref BuildSettings parent, const scope ref BuildPlatform platform) 748 { 749 import dub.compilers.utils : isLinkerFile; 750 751 parent.addDFlags(child.dflags); 752 parent.addVersions(child.versions); 753 parent.addDebugVersions(child.debugVersions); 754 parent.addVersionFilters(child.versionFilters); 755 parent.addDebugVersionFilters(child.debugVersionFilters); 756 parent.addImportPaths(child.importPaths); 757 parent.addCImportPaths(child.cImportPaths); 758 parent.addStringImportPaths(child.stringImportPaths); 759 parent.addInjectSourceFiles(child.injectSourceFiles); 760 // linker stuff propagates up from static *and* dynamic library deps 761 if (child.targetType == TargetType.staticLibrary || child.targetType == TargetType.dynamicLibrary) { 762 parent.addSourceFiles(child.sourceFiles.filter!(f => isLinkerFile(platform, f)).array); 763 parent.addLibs(child.libs); 764 parent.addFrameworks(child.frameworks); 765 parent.addLFlags(child.lflags); 766 } 767 } 768 769 // configure targets for build types such as release, or unittest-cov 770 private void addBuildTypeSettings(TargetInfo[string] targets, in GeneratorSettings settings) 771 { 772 foreach (ref ti; targets.byValue) { 773 ti.buildSettings.add(settings.buildSettings); 774 775 // add build type settings and convert plain DFLAGS to build options 776 m_project.addBuildTypeSettings(ti.buildSettings, settings, ti.pack is m_project.rootPackage); 777 settings.compiler.extractBuildOptions(ti.buildSettings); 778 779 auto tt = ti.buildSettings.targetType; 780 enforce (tt != TargetType.sourceLibrary || ti.pack !is m_project.rootPackage || (ti.buildSettings.options & BuildOption.syntaxOnly), 781 format("Main package must not have target type \"%s\". Cannot build.", tt)); 782 } 783 } 784 } 785 786 /** 787 * Compute and returns the path were artifacts are stored for a given package 788 * 789 * Artifacts are stored in: 790 * `$DUB_HOME/cache/$PKG_NAME/$PKG_VERSION[/+$SUB_PKG_NAME]/` 791 * Note that the leading `+` in the sub-package name is to avoid any ambiguity. 792 * 793 * Dub writes in the returned path a Json description file of the available 794 * artifacts in this cache location. This Json file is read by 3rd party 795 * software (e.g. Meson). Returned path should therefore not change across 796 * future Dub versions. 797 * 798 * Build artifacts are usually stored in a sub-folder named "build", 799 * as their names are based on user-supplied values. 800 * 801 * Params: 802 * cachePath = Base path at which the build cache is located, 803 * e.g. `$HOME/.dub/cache/` 804 * pkg = The package. Cannot be `null`. 805 */ 806 package(dub) NativePath packageCache(NativePath cachePath, in Package pkg) 807 { 808 import std.algorithm.searching : findSplit; 809 810 assert(pkg !is null); 811 assert(!cachePath.empty); 812 813 // For subpackages 814 if (const names = pkg.name.findSplit(":")) 815 return cachePath ~ names[0] ~ pkg.version_.toString() 816 ~ ("+" ~ names[2]); 817 // For regular packages 818 return cachePath ~ pkg.name ~ pkg.version_.toString(); 819 } 820 821 /** 822 * Compute and return the directory where a target should be cached. 823 * 824 * Params: 825 * cachePath = Base path at which the build cache is located, 826 * e.g. `$HOME/.dub/cache/` 827 * pkg = The package. Cannot be `null`. 828 * buildId = The build identifier of the target. 829 */ 830 package(dub) NativePath targetCacheDir(NativePath cachePath, in Package pkg, string buildId) 831 { 832 return packageCache(cachePath, pkg) ~ "build" ~ buildId; 833 } 834 835 /** 836 * Provides a unique (per build) identifier 837 * 838 * When building a package, it is important to have a unique but stable 839 * identifier to differentiate builds and allow their caching. 840 * This function provides such an identifier. 841 * Example: 842 * ``` 843 * library-debug-Z7qINYX4IxM8muBSlyNGrw 844 * ``` 845 */ 846 package(dub) string computeBuildID(in BuildSettings buildsettings, 847 in NativePath packagePath, string config, GeneratorSettings settings) 848 { 849 import std.conv : to; 850 851 const(string[])[] hashing = [ 852 buildsettings.versions, 853 buildsettings.debugVersions, 854 buildsettings.dflags, 855 buildsettings.lflags, 856 buildsettings.stringImportPaths, 857 buildsettings.importPaths, 858 buildsettings.cImportPaths, 859 settings.platform.architecture, 860 [ 861 (cast(uint)(buildsettings.options & ~BuildOption.color)).to!string, // exclude color option from id 862 // Needed for things such as `__FULL_FILE_PATH__` 863 packagePath.toNativeString(), 864 settings.platform.compilerBinary, 865 settings.platform.compiler, 866 settings.platform.compilerVersion, 867 ], 868 ]; 869 870 return computeBuildName(config, settings, hashing); 871 } 872 873 struct GeneratorSettings { 874 NativePath cache; 875 BuildPlatform platform; 876 Compiler compiler; 877 string config; 878 string recipeName; 879 string buildType; 880 BuildSettings buildSettings; 881 BuildMode buildMode = BuildMode.separate; 882 int targetExitStatus; 883 NativePath overrideToolWorkingDirectory; 884 NativePath destinationDirectory; 885 886 bool combined; // compile all in one go instead of each dependency separately 887 bool filterVersions; 888 889 // only used for generator "build" 890 bool run, force, rdmd, tempBuild, parallelBuild; 891 892 /// single file dub package 893 bool single; 894 895 /// build all dependencies for static libraries 896 bool buildDeep; 897 898 string[] runArgs; 899 void delegate(int status, string output) compileCallback; 900 void delegate(int status, string output) linkCallback; 901 void delegate(int status, string output) runCallback; 902 903 /// Returns `overrideToolWorkingDirectory` or if that's not set, just the 904 /// current working directory of the application. This may differ if dub is 905 /// called with the `--root` parameter or when using DUB as a library. 906 NativePath toolWorkingDirectory() const 907 { 908 return overrideToolWorkingDirectory is NativePath.init 909 ? getWorkingDirectory() 910 : overrideToolWorkingDirectory; 911 } 912 } 913 914 915 /** 916 Determines the mode in which the compiler and linker are invoked. 917 */ 918 enum BuildMode { 919 separate, /// Compile and link separately 920 allAtOnce, /// Perform compile and link with a single compiler invocation 921 singleFile, /// Compile each file separately 922 //multipleObjects, /// Generate an object file per module 923 //multipleObjectsPerModule, /// Use the -multiobj switch to generate multiple object files per module 924 //compileOnly /// Do not invoke the linker (can be done using a post build command) 925 } 926 927 928 /** 929 Creates a project generator of the given type for the specified project. 930 */ 931 ProjectGenerator createProjectGenerator(string generator_type, Project project) 932 { 933 assert(project !is null, "Project instance needed to create a generator."); 934 935 generator_type = generator_type.toLower(); 936 switch(generator_type) { 937 default: 938 throw new Exception("Unknown project generator: "~generator_type); 939 case "build": 940 logDebug("Creating build generator."); 941 return new BuildGenerator(project); 942 case "mono-d": 943 throw new Exception("The Mono-D generator has been removed. Use Mono-D's built in DUB support instead."); 944 case "visuald": 945 logDebug("Creating VisualD generator."); 946 return new VisualDGenerator(project); 947 case "sublimetext": 948 logDebug("Creating SublimeText generator."); 949 return new SublimeTextGenerator(project); 950 case "cmake": 951 logDebug("Creating CMake generator."); 952 return new CMakeGenerator(project); 953 } 954 } 955 956 957 /** 958 Calls delegates on files and directories in the given path that match any globs. 959 */ 960 void findFilesMatchingGlobs(in NativePath path, in string[] globList, void delegate(string file) addFile, void delegate(string dir) addDir) 961 { 962 import std.path : globMatch; 963 964 string[] globs; 965 foreach (f; globList) 966 { 967 if (f.canFind("*", "?") || 968 (f.canFind("{") && f.balancedParens('{', '}')) || 969 (f.canFind("[") && f.balancedParens('[', ']'))) 970 { 971 globs ~= f; 972 } 973 else 974 { 975 if (f.isDir) 976 addDir(f); 977 else 978 addFile(f); 979 } 980 } 981 if (globs.length) // Search all files for glob matches 982 foreach (f; dirEntries(path.toNativeString(), SpanMode.breadth)) 983 foreach (glob; globs) 984 if (f.name().globMatch(glob)) 985 { 986 if (f.isDir) 987 addDir(f); 988 else 989 addFile(f); 990 break; 991 } 992 } 993 994 995 /** 996 Calls delegates on files in the given path that match any globs. 997 998 If a directory matches a glob, the delegate is called on all existing files inside it recursively 999 in depth-first pre-order. 1000 */ 1001 void findFilesMatchingGlobs(in NativePath path, in string[] globList, void delegate(string file) addFile) 1002 { 1003 void addDir(string dir) 1004 { 1005 foreach (f; dirEntries(dir, SpanMode.breadth)) 1006 addFile(f); 1007 } 1008 1009 findFilesMatchingGlobs(path, globList, addFile, &addDir); 1010 } 1011 1012 1013 /** 1014 Runs pre-build commands and performs other required setup before project files are generated. 1015 */ 1016 private void prepareGeneration(in Package pack, in Project proj, in GeneratorSettings settings, 1017 in BuildSettings buildsettings) 1018 { 1019 if (buildsettings.preGenerateCommands.length && !isRecursiveInvocation(pack.name)) { 1020 logInfo("Pre-gen", Color.light_green, "Running commands for %s", pack.name); 1021 runBuildCommands(CommandType.preGenerate, buildsettings.preGenerateCommands, pack, proj, settings, buildsettings); 1022 } 1023 } 1024 1025 /** 1026 Runs post-build commands and copies required files to the binary directory. 1027 */ 1028 private void finalizeGeneration(in Package pack, in Project proj, in GeneratorSettings settings, 1029 in BuildSettings buildsettings, NativePath target_path, bool generate_binary) 1030 { 1031 if (buildsettings.postGenerateCommands.length && !isRecursiveInvocation(pack.name)) { 1032 logInfo("Post-gen", Color.light_green, "Running commands for %s", pack.name); 1033 runBuildCommands(CommandType.postGenerate, buildsettings.postGenerateCommands, pack, proj, settings, buildsettings); 1034 } 1035 1036 if (generate_binary) { 1037 if (!settings.tempBuild) 1038 ensureDirectory(NativePath(buildsettings.targetPath)); 1039 1040 if (buildsettings.copyFiles.length) { 1041 void copyFolderRec(NativePath folder, NativePath dstfolder) 1042 { 1043 ensureDirectory(dstfolder); 1044 foreach (de; iterateDirectory(folder)) { 1045 if (de.isDirectory) { 1046 copyFolderRec(folder ~ de.name, dstfolder ~ de.name); 1047 } else { 1048 try copyFile(folder ~ de.name, dstfolder ~ de.name, true); 1049 catch (Exception e) { 1050 logWarn("Failed to copy file %s: %s", (folder ~ de.name).toNativeString(), e.msg); 1051 } 1052 } 1053 } 1054 } 1055 1056 void tryCopyDir(string file) 1057 { 1058 auto src = NativePath(file); 1059 if (!src.absolute) src = pack.path ~ src; 1060 auto dst = target_path ~ NativePath(file).head; 1061 if (src == dst) { 1062 logDiagnostic("Skipping copy of %s (same source and destination)", file); 1063 return; 1064 } 1065 logDiagnostic(" %s to %s", src.toNativeString(), dst.toNativeString()); 1066 try { 1067 copyFolderRec(src, dst); 1068 } catch(Exception e) logWarn("Failed to copy %s to %s: %s", src.toNativeString(), dst.toNativeString(), e.msg); 1069 } 1070 1071 void tryCopyFile(string file) 1072 { 1073 auto src = NativePath(file); 1074 if (!src.absolute) src = pack.path ~ src; 1075 auto dst = target_path ~ NativePath(file).head; 1076 if (src == dst) { 1077 logDiagnostic("Skipping copy of %s (same source and destination)", file); 1078 return; 1079 } 1080 logDiagnostic(" %s to %s", src.toNativeString(), dst.toNativeString()); 1081 try { 1082 copyFile(src, dst, true); 1083 } catch(Exception e) logWarn("Failed to copy %s to %s: %s", src.toNativeString(), dst.toNativeString(), e.msg); 1084 } 1085 logInfo("Copying files for %s...", pack.name); 1086 findFilesMatchingGlobs(pack.path, buildsettings.copyFiles, &tryCopyFile, &tryCopyDir); 1087 } 1088 1089 } 1090 } 1091 1092 1093 /** Runs a list of build commands for a particular package. 1094 1095 This function sets all DUB specific environment variables and makes sure 1096 that recursive dub invocations are detected and don't result in infinite 1097 command execution loops. The latter could otherwise happen when a command 1098 runs "dub describe" or similar functionality. 1099 */ 1100 void runBuildCommands(CommandType type, in string[] commands, in Package pack, in Project proj, 1101 in GeneratorSettings settings, in BuildSettings build_settings, in string[string][] extraVars = null) 1102 { 1103 import dub.internal.utils : runCommands; 1104 1105 auto env = makeCommandEnvironmentVariables(type, pack, proj, settings, build_settings, extraVars); 1106 auto sub_commands = processVars(proj, pack, settings, commands, false, env); 1107 1108 auto depNames = proj.dependencies.map!((a) => a.name).array(); 1109 storeRecursiveInvokations(env, proj.rootPackage.name ~ depNames); 1110 1111 runCommands(sub_commands, env.collapseEnv, pack.path().toString()); 1112 } 1113 1114 const(string[string])[] makeCommandEnvironmentVariables(CommandType type, 1115 in Package pack, in Project proj, in GeneratorSettings settings, 1116 in BuildSettings build_settings, in string[string][] extraVars = null) 1117 { 1118 import dub.internal.utils : getDUBExePath; 1119 import std.conv : to, text; 1120 import std.process : environment, escapeShellFileName; 1121 1122 string[string] env; 1123 // TODO: do more elaborate things here 1124 // TODO: escape/quote individual items appropriately 1125 env["VERSIONS"] = join(build_settings.versions, " "); 1126 env["LIBS"] = join(build_settings.libs, " "); 1127 env["SOURCE_FILES"] = join(build_settings.sourceFiles, " "); 1128 env["IMPORT_PATHS"] = join(build_settings.importPaths, " "); 1129 env["C_IMPORT_PATHS"] = join(build_settings.cImportPaths, " "); 1130 env["STRING_IMPORT_PATHS"] = join(build_settings.stringImportPaths, " "); 1131 1132 env["DC"] = settings.platform.compilerBinary; 1133 env["DC_BASE"] = settings.platform.compiler; 1134 env["D_FRONTEND_VER"] = to!string(settings.platform.frontendVersion); 1135 1136 env["DUB_EXE"] = getDUBExePath(settings.platform.compilerBinary).toNativeString(); 1137 env["DUB_PLATFORM"] = join(settings.platform.platform, " "); 1138 env["DUB_ARCH"] = join(settings.platform.architecture, " "); 1139 1140 env["DUB_TARGET_TYPE"] = to!string(build_settings.targetType); 1141 env["DUB_TARGET_PATH"] = build_settings.targetPath; 1142 env["DUB_TARGET_NAME"] = build_settings.targetName; 1143 env["DUB_TARGET_EXIT_STATUS"] = settings.targetExitStatus.text; 1144 env["DUB_WORKING_DIRECTORY"] = build_settings.workingDirectory; 1145 env["DUB_MAIN_SOURCE_FILE"] = build_settings.mainSourceFile; 1146 1147 env["DUB_CONFIG"] = settings.config; 1148 env["DUB_BUILD_TYPE"] = settings.buildType; 1149 env["DUB_BUILD_MODE"] = to!string(settings.buildMode); 1150 env["DUB_PACKAGE"] = pack.name; 1151 env["DUB_PACKAGE_DIR"] = pack.path.toNativeString(); 1152 env["DUB_ROOT_PACKAGE"] = proj.rootPackage.name; 1153 env["DUB_ROOT_PACKAGE_DIR"] = proj.rootPackage.path.toNativeString(); 1154 env["DUB_PACKAGE_VERSION"] = pack.version_.toString(); 1155 1156 env["DUB_COMBINED"] = settings.combined? "TRUE" : ""; 1157 env["DUB_RUN"] = settings.run? "TRUE" : ""; 1158 env["DUB_FORCE"] = settings.force? "TRUE" : ""; 1159 env["DUB_RDMD"] = settings.rdmd? "TRUE" : ""; 1160 env["DUB_TEMP_BUILD"] = settings.tempBuild? "TRUE" : ""; 1161 env["DUB_PARALLEL_BUILD"] = settings.parallelBuild? "TRUE" : ""; 1162 1163 env["DUB_RUN_ARGS"] = (cast(string[])settings.runArgs).map!(escapeShellFileName).join(" "); 1164 1165 auto cfgs = proj.getPackageConfigs(settings.platform, settings.config, true); 1166 auto rootPackageBuildSettings = proj.rootPackage.getBuildSettings(settings.platform, cfgs[proj.rootPackage.name]); 1167 env["DUB_ROOT_PACKAGE_TARGET_TYPE"] = to!string(rootPackageBuildSettings.targetType); 1168 env["DUB_ROOT_PACKAGE_TARGET_PATH"] = rootPackageBuildSettings.targetPath; 1169 env["DUB_ROOT_PACKAGE_TARGET_NAME"] = rootPackageBuildSettings.targetName; 1170 1171 const(string[string])[] typeEnvVars; 1172 with (build_settings) final switch (type) 1173 { 1174 // pre/postGenerate don't have generateEnvironments, but reuse buildEnvironments 1175 case CommandType.preGenerate: typeEnvVars = [environments, buildEnvironments, preGenerateEnvironments]; break; 1176 case CommandType.postGenerate: typeEnvVars = [environments, buildEnvironments, postGenerateEnvironments]; break; 1177 case CommandType.preBuild: typeEnvVars = [environments, buildEnvironments, preBuildEnvironments]; break; 1178 case CommandType.postBuild: typeEnvVars = [environments, buildEnvironments, postBuildEnvironments]; break; 1179 case CommandType.preRun: typeEnvVars = [environments, runEnvironments, preRunEnvironments]; break; 1180 case CommandType.postRun: typeEnvVars = [environments, runEnvironments, postRunEnvironments]; break; 1181 } 1182 1183 return [environment.toAA()] ~ env ~ typeEnvVars ~ extraVars; 1184 } 1185 1186 string[string] collapseEnv(in string[string][] envs) 1187 { 1188 string[string] ret; 1189 foreach (subEnv; envs) 1190 { 1191 foreach (k, v; subEnv) 1192 ret[k] = v; 1193 } 1194 return ret; 1195 } 1196 1197 /// Type to specify where CLI commands that need to be run came from. Needed for 1198 /// proper substitution with support for the different environments. 1199 enum CommandType 1200 { 1201 /// Defined in the preGenerateCommands setting 1202 preGenerate, 1203 /// Defined in the postGenerateCommands setting 1204 postGenerate, 1205 /// Defined in the preBuildCommands setting 1206 preBuild, 1207 /// Defined in the postBuildCommands setting 1208 postBuild, 1209 /// Defined in the preRunCommands setting 1210 preRun, 1211 /// Defined in the postRunCommands setting 1212 postRun 1213 } 1214 1215 private bool isRecursiveInvocation(string pack) 1216 { 1217 import std.algorithm : canFind, splitter; 1218 import std.process : environment; 1219 1220 return environment 1221 .get("DUB_PACKAGES_USED", "") 1222 .splitter(",") 1223 .canFind(pack); 1224 } 1225 1226 private void storeRecursiveInvokations(ref const(string[string])[] env, string[] packs) 1227 { 1228 import std.algorithm : canFind, splitter; 1229 import std.range : chain; 1230 import std.process : environment; 1231 1232 env ~= [ 1233 "DUB_PACKAGES_USED": environment 1234 .get("DUB_PACKAGES_USED", "") 1235 .splitter(",") 1236 .chain(packs) 1237 .join(",") 1238 ]; 1239 } 1240 1241 version(Posix) { 1242 // https://github.com/dlang/dub/issues/2238 1243 unittest { 1244 import dub.recipe.io : parsePackageRecipe; 1245 import dub.compilers.gdc : GDCCompiler; 1246 import std.algorithm : canFind; 1247 import std.path : absolutePath; 1248 import std.file : rmdirRecurse, write; 1249 1250 mkdirRecurse("dubtest/preGen/source"); 1251 write("dubtest/preGen/source/foo.d", ""); 1252 scope(exit) rmdirRecurse("dubtest"); 1253 1254 auto recipe = parsePackageRecipe( 1255 `{"name":"test", "targetType":"library", "preGenerateCommands":["touch $PACKAGE_DIR/source/bar.d"]}`, 1256 `dubtest/preGen/dub.json`); 1257 auto pack = new Package(recipe, NativePath("dubtest/preGen".absolutePath)); 1258 auto pman = new PackageManager(pack.path, NativePath("/tmp/foo/"), NativePath("/tmp/foo/"), false); 1259 auto prj = new Project(pman, pack); 1260 1261 final static class TestCompiler : GDCCompiler { 1262 override void invoke(in BuildSettings settings, in BuildPlatform platform, void delegate(int, string) output_callback, NativePath cwd) { 1263 assert(false); 1264 } 1265 override void invokeLinker(in BuildSettings settings, in BuildPlatform platform, string[] objects, void delegate(int, string) output_callback, NativePath cwd) { 1266 assert(false); 1267 } 1268 } 1269 1270 GeneratorSettings settings; 1271 settings.compiler = new TestCompiler; 1272 settings.buildType = "debug"; 1273 1274 final static class TestGenerator : ProjectGenerator { 1275 this(Project project) { 1276 super(project); 1277 } 1278 1279 override void generateTargets(GeneratorSettings settings, in TargetInfo[string] targets) { 1280 import std.conv : text; 1281 const sourceFiles = targets["test"].buildSettings.sourceFiles; 1282 assert(sourceFiles.canFind("dubtest/preGen/source/bar.d".absolutePath), sourceFiles.text); 1283 } 1284 } 1285 1286 auto gen = new TestGenerator(prj); 1287 gen.generate(settings); 1288 } 1289 }