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.addCImportPaths(child.cImportPaths); 744 parent.addStringImportPaths(child.stringImportPaths); 745 parent.addInjectSourceFiles(child.injectSourceFiles); 746 // linker stuff propagates up from static *and* dynamic library deps 747 if (child.targetType == TargetType.staticLibrary || child.targetType == TargetType.dynamicLibrary) { 748 parent.addSourceFiles(child.sourceFiles.filter!(f => isLinkerFile(platform, f)).array); 749 parent.addLibs(child.libs); 750 parent.addLFlags(child.lflags); 751 } 752 } 753 754 // configure targets for build types such as release, or unittest-cov 755 private void addBuildTypeSettings(TargetInfo[string] targets, in GeneratorSettings settings) 756 { 757 foreach (ref ti; targets.byValue) { 758 ti.buildSettings.add(settings.buildSettings); 759 760 // add build type settings and convert plain DFLAGS to build options 761 m_project.addBuildTypeSettings(ti.buildSettings, settings, ti.pack is m_project.rootPackage); 762 settings.compiler.extractBuildOptions(ti.buildSettings); 763 764 auto tt = ti.buildSettings.targetType; 765 enforce (tt != TargetType.sourceLibrary || ti.pack !is m_project.rootPackage || (ti.buildSettings.options & BuildOption.syntaxOnly), 766 format("Main package must not have target type \"%s\". Cannot build.", tt)); 767 } 768 } 769 } 770 771 /** 772 * Compute and returns the path were artifacts are stored for a given package 773 * 774 * Artifacts are stored in: 775 * `$DUB_HOME/cache/$PKG_NAME/$PKG_VERSION[/+$SUB_PKG_NAME]/` 776 * Note that the leading `+` in the sub-package name is to avoid any ambiguity. 777 * 778 * Dub writes in the returned path a Json description file of the available 779 * artifacts in this cache location. This Json file is read by 3rd party 780 * software (e.g. Meson). Returned path should therefore not change across 781 * future Dub versions. 782 * 783 * Build artifacts are usually stored in a sub-folder named "build", 784 * as their names are based on user-supplied values. 785 * 786 * Params: 787 * cachePath = Base path at which the build cache is located, 788 * e.g. `$HOME/.dub/cache/` 789 * pkg = The package. Cannot be `null`. 790 */ 791 package(dub) NativePath packageCache(NativePath cachePath, in Package pkg) 792 { 793 import std.algorithm.searching : findSplit; 794 795 assert(pkg !is null); 796 assert(!cachePath.empty); 797 798 // For subpackages 799 if (const names = pkg.name.findSplit(":")) 800 return cachePath ~ names[0] ~ pkg.version_.toString() 801 ~ ("+" ~ names[2]); 802 // For regular packages 803 return cachePath ~ pkg.name ~ pkg.version_.toString(); 804 } 805 806 /** 807 * Compute and return the directory where a target should be cached. 808 * 809 * Params: 810 * cachePath = Base path at which the build cache is located, 811 * e.g. `$HOME/.dub/cache/` 812 * pkg = The package. Cannot be `null`. 813 * buildId = The build identifier of the target. 814 */ 815 package(dub) NativePath targetCacheDir(NativePath cachePath, in Package pkg, string buildId) 816 { 817 return packageCache(cachePath, pkg) ~ "build" ~ buildId; 818 } 819 820 /** 821 * Provides a unique (per build) identifier 822 * 823 * When building a package, it is important to have a unique but stable 824 * identifier to differentiate builds and allow their caching. 825 * This function provides such an identifier. 826 * Example: 827 * ``` 828 * library-debug-Z7qINYX4IxM8muBSlyNGrw 829 * ``` 830 */ 831 package(dub) string computeBuildID(in BuildSettings buildsettings, 832 in NativePath packagePath, string config, GeneratorSettings settings) 833 { 834 import std.conv : to; 835 836 const(string[])[] hashing = [ 837 buildsettings.versions, 838 buildsettings.debugVersions, 839 buildsettings.dflags, 840 buildsettings.lflags, 841 buildsettings.stringImportPaths, 842 buildsettings.importPaths, 843 buildsettings.cImportPaths, 844 settings.platform.architecture, 845 [ 846 (cast(uint)(buildsettings.options & ~BuildOption.color)).to!string, // exclude color option from id 847 // Needed for things such as `__FULL_FILE_PATH__` 848 packagePath.toNativeString(), 849 settings.platform.compilerBinary, 850 settings.platform.compiler, 851 settings.platform.compilerVersion, 852 ], 853 ]; 854 855 return computeBuildName(config, settings, hashing); 856 } 857 858 struct GeneratorSettings { 859 NativePath cache; 860 BuildPlatform platform; 861 Compiler compiler; 862 string config; 863 string recipeName; 864 string buildType; 865 BuildSettings buildSettings; 866 BuildMode buildMode = BuildMode.separate; 867 int targetExitStatus; 868 NativePath overrideToolWorkingDirectory; 869 870 bool combined; // compile all in one go instead of each dependency separately 871 bool filterVersions; 872 873 // only used for generator "build" 874 bool run, force, rdmd, tempBuild, parallelBuild; 875 876 /// single file dub package 877 bool single; 878 879 /// build all dependencies for static libraries 880 bool buildDeep; 881 882 string[] runArgs; 883 void delegate(int status, string output) compileCallback; 884 void delegate(int status, string output) linkCallback; 885 void delegate(int status, string output) runCallback; 886 887 /// Returns `overrideToolWorkingDirectory` or if that's not set, just the 888 /// current working directory of the application. This may differ if dub is 889 /// called with the `--root` parameter or when using DUB as a library. 890 NativePath toolWorkingDirectory() const 891 { 892 return overrideToolWorkingDirectory is NativePath.init 893 ? getWorkingDirectory() 894 : overrideToolWorkingDirectory; 895 } 896 } 897 898 899 /** 900 Determines the mode in which the compiler and linker are invoked. 901 */ 902 enum BuildMode { 903 separate, /// Compile and link separately 904 allAtOnce, /// Perform compile and link with a single compiler invocation 905 singleFile, /// Compile each file separately 906 //multipleObjects, /// Generate an object file per module 907 //multipleObjectsPerModule, /// Use the -multiobj switch to generate multiple object files per module 908 //compileOnly /// Do not invoke the linker (can be done using a post build command) 909 } 910 911 912 /** 913 Creates a project generator of the given type for the specified project. 914 */ 915 ProjectGenerator createProjectGenerator(string generator_type, Project project) 916 { 917 assert(project !is null, "Project instance needed to create a generator."); 918 919 generator_type = generator_type.toLower(); 920 switch(generator_type) { 921 default: 922 throw new Exception("Unknown project generator: "~generator_type); 923 case "build": 924 logDebug("Creating build generator."); 925 return new BuildGenerator(project); 926 case "mono-d": 927 throw new Exception("The Mono-D generator has been removed. Use Mono-D's built in DUB support instead."); 928 case "visuald": 929 logDebug("Creating VisualD generator."); 930 return new VisualDGenerator(project); 931 case "sublimetext": 932 logDebug("Creating SublimeText generator."); 933 return new SublimeTextGenerator(project); 934 case "cmake": 935 logDebug("Creating CMake generator."); 936 return new CMakeGenerator(project); 937 } 938 } 939 940 941 /** 942 Calls delegates on files and directories in the given path that match any globs. 943 */ 944 void findFilesMatchingGlobs(in NativePath path, in string[] globList, void delegate(string file) addFile, void delegate(string dir) addDir) 945 { 946 import std.path : globMatch; 947 948 string[] globs; 949 foreach (f; globList) 950 { 951 if (f.canFind("*", "?") || 952 (f.canFind("{") && f.balancedParens('{', '}')) || 953 (f.canFind("[") && f.balancedParens('[', ']'))) 954 { 955 globs ~= f; 956 } 957 else 958 { 959 if (f.isDir) 960 addDir(f); 961 else 962 addFile(f); 963 } 964 } 965 if (globs.length) // Search all files for glob matches 966 foreach (f; dirEntries(path.toNativeString(), SpanMode.breadth)) 967 foreach (glob; globs) 968 if (f.name().globMatch(glob)) 969 { 970 if (f.isDir) 971 addDir(f); 972 else 973 addFile(f); 974 break; 975 } 976 } 977 978 979 /** 980 Calls delegates on files in the given path that match any globs. 981 982 If a directory matches a glob, the delegate is called on all existing files inside it recursively 983 in depth-first pre-order. 984 */ 985 void findFilesMatchingGlobs(in NativePath path, in string[] globList, void delegate(string file) addFile) 986 { 987 void addDir(string dir) 988 { 989 foreach (f; dirEntries(dir, SpanMode.breadth)) 990 addFile(f); 991 } 992 993 findFilesMatchingGlobs(path, globList, addFile, &addDir); 994 } 995 996 997 /** 998 Runs pre-build commands and performs other required setup before project files are generated. 999 */ 1000 private void prepareGeneration(in Package pack, in Project proj, in GeneratorSettings settings, 1001 in BuildSettings buildsettings) 1002 { 1003 if (buildsettings.preGenerateCommands.length && !isRecursiveInvocation(pack.name)) { 1004 logInfo("Pre-gen", Color.light_green, "Running commands for %s", pack.name); 1005 runBuildCommands(CommandType.preGenerate, buildsettings.preGenerateCommands, pack, proj, settings, buildsettings); 1006 } 1007 } 1008 1009 /** 1010 Runs post-build commands and copies required files to the binary directory. 1011 */ 1012 private void finalizeGeneration(in Package pack, in Project proj, in GeneratorSettings settings, 1013 in BuildSettings buildsettings, NativePath target_path, bool generate_binary) 1014 { 1015 if (buildsettings.postGenerateCommands.length && !isRecursiveInvocation(pack.name)) { 1016 logInfo("Post-gen", Color.light_green, "Running commands for %s", pack.name); 1017 runBuildCommands(CommandType.postGenerate, buildsettings.postGenerateCommands, pack, proj, settings, buildsettings); 1018 } 1019 1020 if (generate_binary) { 1021 if (!settings.tempBuild) 1022 ensureDirectory(NativePath(buildsettings.targetPath)); 1023 1024 if (buildsettings.copyFiles.length) { 1025 void copyFolderRec(NativePath folder, NativePath dstfolder) 1026 { 1027 ensureDirectory(dstfolder); 1028 foreach (de; iterateDirectory(folder)) { 1029 if (de.isDirectory) { 1030 copyFolderRec(folder ~ de.name, dstfolder ~ de.name); 1031 } else { 1032 try hardLinkFile(folder ~ de.name, dstfolder ~ de.name, true); 1033 catch (Exception e) { 1034 logWarn("Failed to copy file %s: %s", (folder ~ de.name).toNativeString(), e.msg); 1035 } 1036 } 1037 } 1038 } 1039 1040 void tryCopyDir(string file) 1041 { 1042 auto src = NativePath(file); 1043 if (!src.absolute) src = pack.path ~ src; 1044 auto dst = target_path ~ NativePath(file).head; 1045 if (src == dst) { 1046 logDiagnostic("Skipping copy of %s (same source and destination)", file); 1047 return; 1048 } 1049 logDiagnostic(" %s to %s", src.toNativeString(), dst.toNativeString()); 1050 try { 1051 copyFolderRec(src, dst); 1052 } catch(Exception e) logWarn("Failed to copy %s to %s: %s", src.toNativeString(), dst.toNativeString(), e.msg); 1053 } 1054 1055 void tryCopyFile(string file) 1056 { 1057 auto src = NativePath(file); 1058 if (!src.absolute) src = pack.path ~ src; 1059 auto dst = target_path ~ NativePath(file).head; 1060 if (src == dst) { 1061 logDiagnostic("Skipping copy of %s (same source and destination)", file); 1062 return; 1063 } 1064 logDiagnostic(" %s to %s", src.toNativeString(), dst.toNativeString()); 1065 try { 1066 hardLinkFile(src, dst, true); 1067 } catch(Exception e) logWarn("Failed to copy %s to %s: %s", src.toNativeString(), dst.toNativeString(), e.msg); 1068 } 1069 logInfo("Copying files for %s...", pack.name); 1070 findFilesMatchingGlobs(pack.path, buildsettings.copyFiles, &tryCopyFile, &tryCopyDir); 1071 } 1072 1073 } 1074 } 1075 1076 1077 /** Runs a list of build commands for a particular package. 1078 1079 This function sets all DUB specific environment variables and makes sure 1080 that recursive dub invocations are detected and don't result in infinite 1081 command execution loops. The latter could otherwise happen when a command 1082 runs "dub describe" or similar functionality. 1083 */ 1084 void runBuildCommands(CommandType type, in string[] commands, in Package pack, in Project proj, 1085 in GeneratorSettings settings, in BuildSettings build_settings, in string[string][] extraVars = null) 1086 { 1087 import dub.internal.utils : runCommands; 1088 1089 auto env = makeCommandEnvironmentVariables(type, pack, proj, settings, build_settings, extraVars); 1090 auto sub_commands = processVars(proj, pack, settings, commands, false, env); 1091 1092 auto depNames = proj.dependencies.map!((a) => a.name).array(); 1093 storeRecursiveInvokations(env, proj.rootPackage.name ~ depNames); 1094 1095 runCommands(sub_commands, env.collapseEnv, pack.path().toString()); 1096 } 1097 1098 const(string[string])[] makeCommandEnvironmentVariables(CommandType type, 1099 in Package pack, in Project proj, in GeneratorSettings settings, 1100 in BuildSettings build_settings, in string[string][] extraVars = null) 1101 { 1102 import dub.internal.utils : getDUBExePath; 1103 import std.conv : to, text; 1104 import std.process : environment, escapeShellFileName; 1105 1106 string[string] env; 1107 // TODO: do more elaborate things here 1108 // TODO: escape/quote individual items appropriately 1109 env["VERSIONS"] = join(build_settings.versions, " "); 1110 env["LIBS"] = join(build_settings.libs, " "); 1111 env["SOURCE_FILES"] = join(build_settings.sourceFiles, " "); 1112 env["IMPORT_PATHS"] = join(build_settings.importPaths, " "); 1113 env["C_IMPORT_PATHS"] = join(build_settings.cImportPaths, " "); 1114 env["STRING_IMPORT_PATHS"] = join(build_settings.stringImportPaths, " "); 1115 1116 env["DC"] = settings.platform.compilerBinary; 1117 env["DC_BASE"] = settings.platform.compiler; 1118 env["D_FRONTEND_VER"] = to!string(settings.platform.frontendVersion); 1119 1120 env["DUB_EXE"] = getDUBExePath(settings.platform.compilerBinary).toNativeString(); 1121 env["DUB_PLATFORM"] = join(settings.platform.platform, " "); 1122 env["DUB_ARCH"] = join(settings.platform.architecture, " "); 1123 1124 env["DUB_TARGET_TYPE"] = to!string(build_settings.targetType); 1125 env["DUB_TARGET_PATH"] = build_settings.targetPath; 1126 env["DUB_TARGET_NAME"] = build_settings.targetName; 1127 env["DUB_TARGET_EXIT_STATUS"] = settings.targetExitStatus.text; 1128 env["DUB_WORKING_DIRECTORY"] = build_settings.workingDirectory; 1129 env["DUB_MAIN_SOURCE_FILE"] = build_settings.mainSourceFile; 1130 1131 env["DUB_CONFIG"] = settings.config; 1132 env["DUB_BUILD_TYPE"] = settings.buildType; 1133 env["DUB_BUILD_MODE"] = to!string(settings.buildMode); 1134 env["DUB_PACKAGE"] = pack.name; 1135 env["DUB_PACKAGE_DIR"] = pack.path.toNativeString(); 1136 env["DUB_ROOT_PACKAGE"] = proj.rootPackage.name; 1137 env["DUB_ROOT_PACKAGE_DIR"] = proj.rootPackage.path.toNativeString(); 1138 env["DUB_PACKAGE_VERSION"] = pack.version_.toString(); 1139 1140 env["DUB_COMBINED"] = settings.combined? "TRUE" : ""; 1141 env["DUB_RUN"] = settings.run? "TRUE" : ""; 1142 env["DUB_FORCE"] = settings.force? "TRUE" : ""; 1143 env["DUB_RDMD"] = settings.rdmd? "TRUE" : ""; 1144 env["DUB_TEMP_BUILD"] = settings.tempBuild? "TRUE" : ""; 1145 env["DUB_PARALLEL_BUILD"] = settings.parallelBuild? "TRUE" : ""; 1146 1147 env["DUB_RUN_ARGS"] = (cast(string[])settings.runArgs).map!(escapeShellFileName).join(" "); 1148 1149 auto cfgs = proj.getPackageConfigs(settings.platform, settings.config, true); 1150 auto rootPackageBuildSettings = proj.rootPackage.getBuildSettings(settings.platform, cfgs[proj.rootPackage.name]); 1151 env["DUB_ROOT_PACKAGE_TARGET_TYPE"] = to!string(rootPackageBuildSettings.targetType); 1152 env["DUB_ROOT_PACKAGE_TARGET_PATH"] = rootPackageBuildSettings.targetPath; 1153 env["DUB_ROOT_PACKAGE_TARGET_NAME"] = rootPackageBuildSettings.targetName; 1154 1155 const(string[string])[] typeEnvVars; 1156 with (build_settings) final switch (type) 1157 { 1158 // pre/postGenerate don't have generateEnvironments, but reuse buildEnvironments 1159 case CommandType.preGenerate: typeEnvVars = [environments, buildEnvironments, preGenerateEnvironments]; break; 1160 case CommandType.postGenerate: typeEnvVars = [environments, buildEnvironments, postGenerateEnvironments]; break; 1161 case CommandType.preBuild: typeEnvVars = [environments, buildEnvironments, preBuildEnvironments]; break; 1162 case CommandType.postBuild: typeEnvVars = [environments, buildEnvironments, postBuildEnvironments]; break; 1163 case CommandType.preRun: typeEnvVars = [environments, runEnvironments, preRunEnvironments]; break; 1164 case CommandType.postRun: typeEnvVars = [environments, runEnvironments, postRunEnvironments]; break; 1165 } 1166 1167 return [environment.toAA()] ~ env ~ typeEnvVars ~ extraVars; 1168 } 1169 1170 string[string] collapseEnv(in string[string][] envs) 1171 { 1172 string[string] ret; 1173 foreach (subEnv; envs) 1174 { 1175 foreach (k, v; subEnv) 1176 ret[k] = v; 1177 } 1178 return ret; 1179 } 1180 1181 /// Type to specify where CLI commands that need to be run came from. Needed for 1182 /// proper substitution with support for the different environments. 1183 enum CommandType 1184 { 1185 /// Defined in the preGenerateCommands setting 1186 preGenerate, 1187 /// Defined in the postGenerateCommands setting 1188 postGenerate, 1189 /// Defined in the preBuildCommands setting 1190 preBuild, 1191 /// Defined in the postBuildCommands setting 1192 postBuild, 1193 /// Defined in the preRunCommands setting 1194 preRun, 1195 /// Defined in the postRunCommands setting 1196 postRun 1197 } 1198 1199 private bool isRecursiveInvocation(string pack) 1200 { 1201 import std.algorithm : canFind, splitter; 1202 import std.process : environment; 1203 1204 return environment 1205 .get("DUB_PACKAGES_USED", "") 1206 .splitter(",") 1207 .canFind(pack); 1208 } 1209 1210 private void storeRecursiveInvokations(ref const(string[string])[] env, string[] packs) 1211 { 1212 import std.algorithm : canFind, splitter; 1213 import std.range : chain; 1214 import std.process : environment; 1215 1216 env ~= [ 1217 "DUB_PACKAGES_USED": environment 1218 .get("DUB_PACKAGES_USED", "") 1219 .splitter(",") 1220 .chain(packs) 1221 .join(",") 1222 ]; 1223 } 1224 1225 version(Posix) { 1226 // https://github.com/dlang/dub/issues/2238 1227 unittest { 1228 import dub.internal.vibecompat.data.json : parseJsonString; 1229 import dub.compilers.gdc : GDCCompiler; 1230 import std.algorithm : canFind; 1231 import std.path : absolutePath; 1232 import std.file : rmdirRecurse, write; 1233 1234 mkdirRecurse("dubtest/preGen/source"); 1235 write("dubtest/preGen/source/foo.d", ""); 1236 scope(exit) rmdirRecurse("dubtest"); 1237 1238 auto desc = parseJsonString(`{"name": "test", "targetType": "library", "preGenerateCommands": ["touch $PACKAGE_DIR/source/bar.d"]}`); 1239 auto pack = new Package(desc, NativePath("dubtest/preGen".absolutePath)); 1240 auto pman = new PackageManager(pack.path, NativePath("/tmp/foo/"), NativePath("/tmp/foo/"), false); 1241 auto prj = new Project(pman, pack); 1242 1243 final static class TestCompiler : GDCCompiler { 1244 override void invoke(in BuildSettings settings, in BuildPlatform platform, void delegate(int, string) output_callback, NativePath cwd) { 1245 assert(false); 1246 } 1247 override void invokeLinker(in BuildSettings settings, in BuildPlatform platform, string[] objects, void delegate(int, string) output_callback, NativePath cwd) { 1248 assert(false); 1249 } 1250 } 1251 1252 GeneratorSettings settings; 1253 settings.compiler = new TestCompiler; 1254 settings.buildType = "debug"; 1255 1256 final static class TestGenerator : ProjectGenerator { 1257 this(Project project) { 1258 super(project); 1259 } 1260 1261 override void generateTargets(GeneratorSettings settings, in TargetInfo[string] targets) { 1262 import std.conv : text; 1263 const sourceFiles = targets["test"].buildSettings.sourceFiles; 1264 assert(sourceFiles.canFind("dubtest/preGen/source/bar.d".absolutePath), sourceFiles.text); 1265 } 1266 } 1267 1268 auto gen = new TestGenerator(prj); 1269 gen.generate(settings); 1270 } 1271 }