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