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