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