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 subpackage name is to avoid any ambiguity. 776 * Build artifacts are usually stored in a subfolder 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 810 bool combined; // compile all in one go instead of each dependency separately 811 bool filterVersions; 812 813 // only used for generator "build" 814 bool run, force, direct, rdmd, tempBuild, parallelBuild; 815 816 /// single file dub package 817 bool single; 818 819 string[] runArgs; 820 void delegate(int status, string output) compileCallback; 821 void delegate(int status, string output) linkCallback; 822 void delegate(int status, string output) runCallback; 823 } 824 825 826 /** 827 Determines the mode in which the compiler and linker are invoked. 828 */ 829 enum BuildMode { 830 separate, /// Compile and link separately 831 allAtOnce, /// Perform compile and link with a single compiler invocation 832 singleFile, /// Compile each file separately 833 //multipleObjects, /// Generate an object file per module 834 //multipleObjectsPerModule, /// Use the -multiobj switch to generate multiple object files per module 835 //compileOnly /// Do not invoke the linker (can be done using a post build command) 836 } 837 838 839 /** 840 Creates a project generator of the given type for the specified project. 841 */ 842 ProjectGenerator createProjectGenerator(string generator_type, Project project) 843 { 844 assert(project !is null, "Project instance needed to create a generator."); 845 846 generator_type = generator_type.toLower(); 847 switch(generator_type) { 848 default: 849 throw new Exception("Unknown project generator: "~generator_type); 850 case "build": 851 logDebug("Creating build generator."); 852 return new BuildGenerator(project); 853 case "mono-d": 854 throw new Exception("The Mono-D generator has been removed. Use Mono-D's built in DUB support instead."); 855 case "visuald": 856 logDebug("Creating VisualD generator."); 857 return new VisualDGenerator(project); 858 case "sublimetext": 859 logDebug("Creating SublimeText generator."); 860 return new SublimeTextGenerator(project); 861 case "cmake": 862 logDebug("Creating CMake generator."); 863 return new CMakeGenerator(project); 864 } 865 } 866 867 868 /** 869 Calls delegates on files and directories in the given path that match any globs. 870 */ 871 void findFilesMatchingGlobs(in NativePath path, in string[] globList, void delegate(string file) addFile, void delegate(string dir) addDir) 872 { 873 import std.path : globMatch; 874 875 string[] globs; 876 foreach (f; globList) 877 { 878 if (f.canFind("*", "?") || 879 (f.canFind("{") && f.balancedParens('{', '}')) || 880 (f.canFind("[") && f.balancedParens('[', ']'))) 881 { 882 globs ~= f; 883 } 884 else 885 { 886 if (f.isDir) 887 addDir(f); 888 else 889 addFile(f); 890 } 891 } 892 if (globs.length) // Search all files for glob matches 893 foreach (f; dirEntries(path.toNativeString(), SpanMode.breadth)) 894 foreach (glob; globs) 895 if (f.name().globMatch(glob)) 896 { 897 if (f.isDir) 898 addDir(f); 899 else 900 addFile(f); 901 break; 902 } 903 } 904 905 906 /** 907 Calls delegates on files in the given path that match any globs. 908 909 If a directory matches a glob, the delegate is called on all existing files inside it recursively 910 in depth-first pre-order. 911 */ 912 void findFilesMatchingGlobs(in NativePath path, in string[] globList, void delegate(string file) addFile) 913 { 914 void addDir(string dir) 915 { 916 foreach (f; dirEntries(dir, SpanMode.breadth)) 917 addFile(f); 918 } 919 920 findFilesMatchingGlobs(path, globList, addFile, &addDir); 921 } 922 923 924 /** 925 Runs pre-build commands and performs other required setup before project files are generated. 926 */ 927 private void prepareGeneration(in Package pack, in Project proj, in GeneratorSettings settings, 928 in BuildSettings buildsettings) 929 { 930 if (buildsettings.preGenerateCommands.length && !isRecursiveInvocation(pack.name)) { 931 logInfo("Pre-gen", Color.light_green, "Running commands for %s", pack.name); 932 runBuildCommands(CommandType.preGenerate, buildsettings.preGenerateCommands, pack, proj, settings, buildsettings); 933 } 934 } 935 936 /** 937 Runs post-build commands and copies required files to the binary directory. 938 */ 939 private void finalizeGeneration(in Package pack, in Project proj, in GeneratorSettings settings, 940 in BuildSettings buildsettings, NativePath target_path, bool generate_binary) 941 { 942 if (buildsettings.postGenerateCommands.length && !isRecursiveInvocation(pack.name)) { 943 logInfo("Post-gen", Color.light_green, "Running commands for %s", pack.name); 944 runBuildCommands(CommandType.postGenerate, buildsettings.postGenerateCommands, pack, proj, settings, buildsettings); 945 } 946 947 if (generate_binary) { 948 ensureDirectory(NativePath(buildsettings.targetPath)); 949 950 if (buildsettings.copyFiles.length) { 951 void copyFolderRec(NativePath folder, NativePath dstfolder) 952 { 953 ensureDirectory(dstfolder); 954 foreach (de; iterateDirectory(folder.toNativeString())) { 955 if (de.isDirectory) { 956 copyFolderRec(folder ~ de.name, dstfolder ~ de.name); 957 } else { 958 try hardLinkFile(folder ~ de.name, dstfolder ~ de.name, true); 959 catch (Exception e) { 960 logWarn("Failed to copy file %s: %s", (folder ~ de.name).toNativeString(), e.msg); 961 } 962 } 963 } 964 } 965 966 void tryCopyDir(string file) 967 { 968 auto src = NativePath(file); 969 if (!src.absolute) src = pack.path ~ src; 970 auto dst = target_path ~ NativePath(file).head; 971 if (src == dst) { 972 logDiagnostic("Skipping copy of %s (same source and destination)", file); 973 return; 974 } 975 logDiagnostic(" %s to %s", src.toNativeString(), dst.toNativeString()); 976 try { 977 copyFolderRec(src, dst); 978 } catch(Exception e) logWarn("Failed to copy %s to %s: %s", src.toNativeString(), dst.toNativeString(), e.msg); 979 } 980 981 void tryCopyFile(string file) 982 { 983 auto src = NativePath(file); 984 if (!src.absolute) src = pack.path ~ src; 985 auto dst = target_path ~ NativePath(file).head; 986 if (src == dst) { 987 logDiagnostic("Skipping copy of %s (same source and destination)", file); 988 return; 989 } 990 logDiagnostic(" %s to %s", src.toNativeString(), dst.toNativeString()); 991 try { 992 hardLinkFile(src, dst, true); 993 } catch(Exception e) logWarn("Failed to copy %s to %s: %s", src.toNativeString(), dst.toNativeString(), e.msg); 994 } 995 logInfo("Copying files for %s...", pack.name); 996 findFilesMatchingGlobs(pack.path, buildsettings.copyFiles, &tryCopyFile, &tryCopyDir); 997 } 998 999 } 1000 } 1001 1002 1003 /** Runs a list of build commands for a particular package. 1004 1005 This function sets all DUB speficic environment variables and makes sure 1006 that recursive dub invocations are detected and don't result in infinite 1007 command execution loops. The latter could otherwise happen when a command 1008 runs "dub describe" or similar functionality. 1009 */ 1010 void runBuildCommands(CommandType type, in string[] commands, in Package pack, in Project proj, 1011 in GeneratorSettings settings, in BuildSettings build_settings, in string[string][] extraVars = null) 1012 { 1013 import dub.internal.utils : runCommands; 1014 1015 auto env = makeCommandEnvironmentVariables(type, pack, proj, settings, build_settings, extraVars); 1016 auto sub_commands = processVars(proj, pack, settings, commands, false, env); 1017 1018 auto depNames = proj.dependencies.map!((a) => a.name).array(); 1019 storeRecursiveInvokations(env, proj.rootPackage.name ~ depNames); 1020 1021 runCommands(sub_commands, env.collapseEnv, pack.path().toString()); 1022 } 1023 1024 const(string[string])[] makeCommandEnvironmentVariables(CommandType type, 1025 in Package pack, in Project proj, in GeneratorSettings settings, 1026 in BuildSettings build_settings, in string[string][] extraVars = null) 1027 { 1028 import dub.internal.utils : getDUBExePath; 1029 import std.conv : to, text; 1030 import std.process : environment, escapeShellFileName; 1031 1032 string[string] env; 1033 // TODO: do more elaborate things here 1034 // TODO: escape/quote individual items appropriately 1035 env["VERSIONS"] = join(build_settings.versions, " "); 1036 env["LIBS"] = join(build_settings.libs, " "); 1037 env["SOURCE_FILES"] = join(build_settings.sourceFiles, " "); 1038 env["IMPORT_PATHS"] = join(build_settings.importPaths, " "); 1039 env["STRING_IMPORT_PATHS"] = join(build_settings.stringImportPaths, " "); 1040 1041 env["DC"] = settings.platform.compilerBinary; 1042 env["DC_BASE"] = settings.platform.compiler; 1043 env["D_FRONTEND_VER"] = to!string(settings.platform.frontendVersion); 1044 1045 env["DUB_EXE"] = getDUBExePath(settings.platform.compilerBinary); 1046 env["DUB_PLATFORM"] = join(settings.platform.platform, " "); 1047 env["DUB_ARCH"] = join(settings.platform.architecture, " "); 1048 1049 env["DUB_TARGET_TYPE"] = to!string(build_settings.targetType); 1050 env["DUB_TARGET_PATH"] = build_settings.targetPath; 1051 env["DUB_TARGET_NAME"] = build_settings.targetName; 1052 env["DUB_TARGET_EXIT_STATUS"] = settings.targetExitStatus.text; 1053 env["DUB_WORKING_DIRECTORY"] = build_settings.workingDirectory; 1054 env["DUB_MAIN_SOURCE_FILE"] = build_settings.mainSourceFile; 1055 1056 env["DUB_CONFIG"] = settings.config; 1057 env["DUB_BUILD_TYPE"] = settings.buildType; 1058 env["DUB_BUILD_MODE"] = to!string(settings.buildMode); 1059 env["DUB_PACKAGE"] = pack.name; 1060 env["DUB_PACKAGE_DIR"] = pack.path.toNativeString(); 1061 env["DUB_ROOT_PACKAGE"] = proj.rootPackage.name; 1062 env["DUB_ROOT_PACKAGE_DIR"] = proj.rootPackage.path.toNativeString(); 1063 env["DUB_PACKAGE_VERSION"] = pack.version_.toString(); 1064 1065 env["DUB_COMBINED"] = settings.combined? "TRUE" : ""; 1066 env["DUB_RUN"] = settings.run? "TRUE" : ""; 1067 env["DUB_FORCE"] = settings.force? "TRUE" : ""; 1068 env["DUB_DIRECT"] = settings.direct? "TRUE" : ""; 1069 env["DUB_RDMD"] = settings.rdmd? "TRUE" : ""; 1070 env["DUB_TEMP_BUILD"] = settings.tempBuild? "TRUE" : ""; 1071 env["DUB_PARALLEL_BUILD"] = settings.parallelBuild? "TRUE" : ""; 1072 1073 env["DUB_RUN_ARGS"] = (cast(string[])settings.runArgs).map!(escapeShellFileName).join(" "); 1074 1075 auto cfgs = proj.getPackageConfigs(settings.platform, settings.config, true); 1076 auto rootPackageBuildSettings = proj.rootPackage.getBuildSettings(settings.platform, cfgs[proj.rootPackage.name]); 1077 env["DUB_ROOT_PACKAGE_TARGET_TYPE"] = to!string(rootPackageBuildSettings.targetType); 1078 env["DUB_ROOT_PACKAGE_TARGET_PATH"] = rootPackageBuildSettings.targetPath; 1079 env["DUB_ROOT_PACKAGE_TARGET_NAME"] = rootPackageBuildSettings.targetName; 1080 1081 const(string[string])[] typeEnvVars; 1082 with (build_settings) final switch (type) 1083 { 1084 // pre/postGenerate don't have generateEnvironments, but reuse buildEnvironments 1085 case CommandType.preGenerate: typeEnvVars = [environments, buildEnvironments, preGenerateEnvironments]; break; 1086 case CommandType.postGenerate: typeEnvVars = [environments, buildEnvironments, postGenerateEnvironments]; break; 1087 case CommandType.preBuild: typeEnvVars = [environments, buildEnvironments, preBuildEnvironments]; break; 1088 case CommandType.postBuild: typeEnvVars = [environments, buildEnvironments, postBuildEnvironments]; break; 1089 case CommandType.preRun: typeEnvVars = [environments, runEnvironments, preRunEnvironments]; break; 1090 case CommandType.postRun: typeEnvVars = [environments, runEnvironments, postRunEnvironments]; break; 1091 } 1092 1093 return [environment.toAA()] ~ env ~ typeEnvVars ~ extraVars; 1094 } 1095 1096 string[string] collapseEnv(in string[string][] envs) 1097 { 1098 string[string] ret; 1099 foreach (subEnv; envs) 1100 { 1101 foreach (k, v; subEnv) 1102 ret[k] = v; 1103 } 1104 return ret; 1105 } 1106 1107 /// Type to specify where CLI commands that need to be run came from. Needed for 1108 /// proper substitution with support for the different environments. 1109 enum CommandType 1110 { 1111 /// Defined in the preGenerateCommands setting 1112 preGenerate, 1113 /// Defined in the postGenerateCommands setting 1114 postGenerate, 1115 /// Defined in the preBuildCommands setting 1116 preBuild, 1117 /// Defined in the postBuildCommands setting 1118 postBuild, 1119 /// Defined in the preRunCommands setting 1120 preRun, 1121 /// Defined in the postRunCommands setting 1122 postRun 1123 } 1124 1125 private bool isRecursiveInvocation(string pack) 1126 { 1127 import std.algorithm : canFind, splitter; 1128 import std.process : environment; 1129 1130 return environment 1131 .get("DUB_PACKAGES_USED", "") 1132 .splitter(",") 1133 .canFind(pack); 1134 } 1135 1136 private void storeRecursiveInvokations(ref const(string[string])[] env, string[] packs) 1137 { 1138 import std.algorithm : canFind, splitter; 1139 import std.range : chain; 1140 import std.process : environment; 1141 1142 env ~= [ 1143 "DUB_PACKAGES_USED": environment 1144 .get("DUB_PACKAGES_USED", "") 1145 .splitter(",") 1146 .chain(packs) 1147 .join(",") 1148 ]; 1149 } 1150 1151 version(Posix) { 1152 // https://github.com/dlang/dub/issues/2238 1153 unittest { 1154 import dub.internal.vibecompat.data.json : parseJsonString; 1155 import dub.compilers.gdc : GDCCompiler; 1156 import std.algorithm : canFind; 1157 import std.path : absolutePath; 1158 import std.file : rmdirRecurse, write; 1159 1160 mkdirRecurse("dubtest/preGen/source"); 1161 write("dubtest/preGen/source/foo.d", ""); 1162 scope(exit) rmdirRecurse("dubtest"); 1163 1164 auto desc = parseJsonString(`{"name": "test", "targetType": "library", "preGenerateCommands": ["touch $PACKAGE_DIR/source/bar.d"]}`); 1165 auto pack = new Package(desc, NativePath("dubtest/preGen".absolutePath)); 1166 auto pman = new PackageManager(pack.path, NativePath("/tmp/foo/"), NativePath("/tmp/foo/"), false); 1167 auto prj = new Project(pman, pack); 1168 1169 final static class TestCompiler : GDCCompiler { 1170 override void invoke(in BuildSettings settings, in BuildPlatform platform, void delegate(int, string) output_callback) { 1171 assert(false); 1172 } 1173 override void invokeLinker(in BuildSettings settings, in BuildPlatform platform, string[] objects, void delegate(int, string) output_callback) { 1174 assert(false); 1175 } 1176 } 1177 1178 GeneratorSettings settings; 1179 settings.compiler = new TestCompiler; 1180 settings.buildType = "debug"; 1181 1182 final static class TestGenerator : ProjectGenerator { 1183 this(Project project) { 1184 super(project); 1185 } 1186 1187 override void generateTargets(GeneratorSettings settings, in TargetInfo[string] targets) { 1188 import std.conv : text; 1189 const sourceFiles = targets["test"].buildSettings.sourceFiles; 1190 assert(sourceFiles.canFind("dubtest/preGen/source/bar.d".absolutePath), sourceFiles.text); 1191 } 1192 } 1193 1194 auto gen = new TestGenerator(prj); 1195 gen.generate(settings); 1196 } 1197 }