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