1 /** 2 Generator for project files 3 4 Copyright: © 2012-2013 Matthias Dondorff, © 2013-2016 Sönke Ludwig 5 License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file. 6 Authors: Matthias Dondorff 7 */ 8 module dub.generators.generator; 9 10 import dub.compilers.compiler; 11 import dub.generators.cmake; 12 import dub.generators.build; 13 import dub.generators.sublimetext; 14 import dub.generators.visuald; 15 import dub.internal.vibecompat.core.file; 16 import dub.internal.vibecompat.core.log; 17 import dub.internal.vibecompat.inet.path; 18 import dub.package_; 19 import dub.packagemanager; 20 import dub.project; 21 22 import std.algorithm : map, filter, canFind, balancedParens; 23 import std.array : array, appender, join; 24 import std.exception; 25 import std.file; 26 import std.string; 27 28 29 /** 30 Common interface for project generators/builders. 31 */ 32 class ProjectGenerator 33 { 34 /** Information about a single binary target. 35 36 A binary target can either be an executable or a static/dynamic library. 37 It consists of one or more packages. 38 */ 39 struct TargetInfo { 40 /// The root package of this target 41 Package pack; 42 43 /// All packages compiled into this target 44 Package[] packages; 45 46 /// The configuration used for building the root package 47 string config; 48 49 /** Build settings used to build the target. 50 51 The build settings include all sources of all contained packages. 52 53 Depending on the specific generator implementation, it may be 54 necessary to add any static or dynamic libraries generated for 55 child targets ($(D linkDependencies)). 56 */ 57 BuildSettings buildSettings; 58 59 /** List of all dependencies. 60 61 This list includes dependencies that are not the root of a binary 62 target. 63 */ 64 string[] dependencies; 65 66 /** List of all binary dependencies. 67 68 This list includes all dependencies that are the root of a binary 69 target. 70 */ 71 string[] linkDependencies; 72 } 73 74 protected { 75 Project m_project; 76 NativePath m_tempTargetExecutablePath; 77 } 78 79 this(Project project) 80 { 81 m_project = project; 82 } 83 84 /** Performs the full generator process. 85 */ 86 final void generate(GeneratorSettings settings) 87 { 88 import dub.compilers.utils : enforceBuildRequirements; 89 90 if (!settings.config.length) settings.config = m_project.getDefaultConfiguration(settings.platform); 91 92 string[string] configs = m_project.getPackageConfigs(settings.platform, settings.config); 93 TargetInfo[string] targets; 94 95 foreach (pack; m_project.getTopologicalPackageList(true, null, configs)) { 96 BuildSettings buildSettings; 97 auto config = configs[pack.name]; 98 buildSettings.processVars(m_project, pack, pack.getBuildSettings(settings.platform, config), settings, true); 99 if (settings.buildSettings.options & BuildOption.lowmem) buildSettings.options |= BuildOption.lowmem; 100 101 prepareGeneration(pack, m_project, settings, buildSettings); 102 103 // Regenerate buildSettings.sourceFiles 104 if (buildSettings.preGenerateCommands.length) { 105 buildSettings = BuildSettings.init; 106 buildSettings.processVars(m_project, pack, pack.getBuildSettings(settings.platform, config), settings, true); 107 } 108 targets[pack.name] = TargetInfo(pack, [pack], config, buildSettings); 109 } 110 111 configurePackages(m_project.rootPackage, targets, settings); 112 113 addBuildTypeSettings(targets, settings); 114 foreach (ref t; targets.byValue) enforceBuildRequirements(t.buildSettings); 115 116 generateTargets(settings, targets); 117 118 foreach (pack; m_project.getTopologicalPackageList(true, null, configs)) { 119 BuildSettings buildsettings; 120 buildsettings.processVars(m_project, pack, pack.getBuildSettings(settings.platform, configs[pack.name]), settings, true); 121 bool generate_binary = !(buildsettings.options & BuildOption.syntaxOnly); 122 auto bs = &targets[m_project.rootPackage.name].buildSettings; 123 auto targetPath = !m_tempTargetExecutablePath.empty ? m_tempTargetExecutablePath : 124 !bs.targetPath.empty ? NativePath(bs.targetPath) : 125 NativePath(buildsettings.targetPath); 126 127 finalizeGeneration(pack, m_project, settings, buildsettings, targetPath, generate_binary); 128 } 129 130 performPostGenerateActions(settings, targets); 131 } 132 133 /** Overridden in derived classes to implement the actual generator functionality. 134 135 The function should go through all targets recursively. The first target 136 (which is guaranteed to be there) is 137 $(D targets[m_project.rootPackage.name]). The recursive descent is then 138 done using the $(D TargetInfo.linkDependencies) list. 139 140 This method is also potentially responsible for running the pre and post 141 build commands, while pre and post generate commands are already taken 142 care of by the $(D generate) method. 143 144 Params: 145 settings = The generator settings used for this run 146 targets = A map from package name to TargetInfo that contains all 147 binary targets to be built. 148 */ 149 protected abstract void generateTargets(GeneratorSettings settings, in TargetInfo[string] targets); 150 151 /** Overridable method to be invoked after the generator process has finished. 152 153 An examples of functionality placed here is to run the application that 154 has just been built. 155 */ 156 protected void performPostGenerateActions(GeneratorSettings settings, in TargetInfo[string] targets) {} 157 158 /** Configure `rootPackage` and all of it's dependencies. 159 160 1. Merge versions, debugVersions, and inheritable build 161 settings from dependents to their dependencies. 162 163 2. Define version identifiers Have_dependency_xyz for all 164 direct dependencies of all packages. 165 166 3. Merge versions, debugVersions, and inheritable build settings from 167 dependencies to their dependents, so that importer and importee are ABI 168 compatible. This also transports all Have_dependency_xyz version 169 identifiers to `rootPackage`. 170 171 4. Filter unused versions and debugVersions from all targets. The 172 filters have previously been upwards inherited (3.) so that versions 173 used in a dependency are also applied to all dependents. 174 175 Note: The upwards inheritance is done at last so that siblings do not 176 influence each other, also see https://github.com/dlang/dub/pull/1128. 177 178 Note: Targets without output are integrated into their 179 dependents and removed from `targets`. 180 */ 181 private void configurePackages(Package rootPackage, TargetInfo[string] targets, GeneratorSettings genSettings) 182 { 183 import std.algorithm : remove, sort; 184 import std.range : repeat; 185 186 auto roottarget = &targets[rootPackage.name]; 187 188 // 0. do shallow configuration (not including dependencies) of all packages 189 TargetType determineTargetType(const ref TargetInfo ti) 190 { 191 TargetType tt = ti.buildSettings.targetType; 192 if (ti.pack is rootPackage) { 193 if (tt == TargetType.autodetect || tt == TargetType.library) tt = TargetType.staticLibrary; 194 } else { 195 if (tt == TargetType.autodetect || tt == TargetType.library) tt = genSettings.combined ? TargetType.sourceLibrary : TargetType.staticLibrary; 196 else if (tt == TargetType.dynamicLibrary) { 197 logWarn("Dynamic libraries are not yet supported as dependencies - building as static library."); 198 tt = TargetType.staticLibrary; 199 } 200 } 201 if (tt != TargetType.none && tt != TargetType.sourceLibrary && ti.buildSettings.sourceFiles.empty) { 202 logWarn(`Configuration '%s' of package %s contains no source files. Please add {"targetType": "none"} to its package description to avoid building it.`, 203 ti.config, ti.pack.name); 204 tt = TargetType.none; 205 } 206 return tt; 207 } 208 209 string[] mainSourceFiles; 210 bool[string] hasOutput; 211 212 foreach (ref ti; targets.byValue) 213 { 214 auto bs = &ti.buildSettings; 215 // determine the actual target type 216 bs.targetType = determineTargetType(ti); 217 218 switch (bs.targetType) 219 { 220 case TargetType.none: 221 // ignore any build settings for targetType none (only dependencies will be processed) 222 *bs = BuildSettings.init; 223 bs.targetType = TargetType.none; 224 break; 225 226 case TargetType.executable: 227 break; 228 229 case TargetType.dynamicLibrary: 230 // set -fPIC for dynamic library builds 231 ti.buildSettings.addOptions(BuildOption.pic); 232 goto default; 233 234 default: 235 // remove any mainSourceFile from non-executable builds 236 if (bs.mainSourceFile.length) { 237 bs.sourceFiles = bs.sourceFiles.remove!(f => f == bs.mainSourceFile); 238 mainSourceFiles ~= bs.mainSourceFile; 239 } 240 break; 241 } 242 bool generatesBinary = bs.targetType != TargetType.sourceLibrary && bs.targetType != TargetType.none; 243 hasOutput[ti.pack.name] = generatesBinary || ti.pack is rootPackage; 244 } 245 246 // add main source files to root executable 247 { 248 auto bs = &roottarget.buildSettings; 249 if (bs.targetType == TargetType.executable || genSettings.single) bs.addSourceFiles(mainSourceFiles); 250 } 251 252 if (genSettings.filterVersions) 253 foreach (ref ti; targets.byValue) 254 inferVersionFilters(ti); 255 256 // mark packages as visited (only used during upwards propagation) 257 void[0][Package] visited; 258 259 // collect all dependencies 260 void collectDependencies(Package pack, ref TargetInfo ti, TargetInfo[string] targets, size_t level = 0) 261 { 262 // use `visited` here as pkgs cannot depend on themselves 263 if (pack in visited) 264 return; 265 // transitive dependencies must be visited multiple times, see #1350 266 immutable transitive = !hasOutput[pack.name]; 267 if (!transitive) 268 visited[pack] = typeof(visited[pack]).init; 269 270 auto bs = &ti.buildSettings; 271 if (hasOutput[pack.name]) 272 logDebug("%sConfiguring target %s (%s %s %s)", ' '.repeat(2 * level), pack.name, bs.targetType, bs.targetPath, bs.targetName); 273 else 274 logDebug("%sConfiguring target without output %s", ' '.repeat(2 * level), pack.name); 275 276 // get specified dependencies, e.g. vibe-d ~0.8.1 277 auto deps = pack.getDependencies(targets[pack.name].config); 278 logDebug("deps: %s -> %(%s, %)", pack.name, deps.byKey); 279 foreach (depname; deps.keys.sort()) 280 { 281 auto depspec = deps[depname]; 282 // get selected package for that dependency, e.g. vibe-d 0.8.2-beta.2 283 auto deppack = m_project.getDependency(depname, depspec.optional); 284 if (deppack is null) continue; // optional and not selected 285 286 // if dependency has no output 287 if (!hasOutput[depname]) { 288 // add itself 289 ti.packages ~= deppack; 290 // and it's transitive dependencies to current target 291 collectDependencies(deppack, ti, targets, level + 1); 292 continue; 293 } 294 auto depti = &targets[depname]; 295 const depbs = &depti.buildSettings; 296 if (depbs.targetType == TargetType.executable && ti.buildSettings.targetType != TargetType.none) 297 continue; 298 299 // add to (link) dependencies 300 ti.dependencies ~= depname; 301 ti.linkDependencies ~= depname; 302 303 // recurse 304 collectDependencies(deppack, *depti, targets, level + 1); 305 306 // also recursively add all link dependencies of static libraries 307 // preserve topological sorting of dependencies for correct link order 308 if (depbs.targetType == TargetType.staticLibrary) 309 ti.linkDependencies = ti.linkDependencies.filter!(d => !depti.linkDependencies.canFind(d)).array ~ depti.linkDependencies; 310 } 311 312 enforce(!(ti.buildSettings.targetType == TargetType.none && ti.dependencies.empty), 313 "Package with target type \"none\" must have dependencies to build."); 314 } 315 316 collectDependencies(rootPackage, *roottarget, targets); 317 visited.clear(); 318 319 // 1. downwards inherits versions, debugVersions, and inheritable build settings 320 static void configureDependencies(const scope ref TargetInfo ti, TargetInfo[string] targets, 321 BuildSettings[string] dependBS, size_t level = 0) 322 { 323 324 static void applyForcedSettings(const scope ref BuildSettings forced, ref BuildSettings child) { 325 child.addDFlags(forced.dflags); 326 } 327 328 // do not use `visited` here as dependencies must inherit 329 // configurations from *all* of their parents 330 logDebug("%sConfigure dependencies of %s, deps:%(%s, %)", ' '.repeat(2 * level), ti.pack.name, ti.dependencies); 331 foreach (depname; ti.dependencies) 332 { 333 BuildSettings forcedSettings; 334 auto pti = &targets[depname]; 335 mergeFromDependent(ti.buildSettings, pti.buildSettings); 336 337 if (auto matchedSettings = depname in dependBS) 338 forcedSettings = *matchedSettings; 339 else if (auto matchedSettings = "*" in dependBS) 340 forcedSettings = *matchedSettings; 341 342 applyForcedSettings(forcedSettings, pti.buildSettings); 343 configureDependencies(*pti, targets, ["*" : forcedSettings], level + 1); 344 } 345 } 346 347 BuildSettings[string] dependencyBuildSettings; 348 foreach (key, value; rootPackage.recipe.buildSettings.dependencyBuildSettings) 349 { 350 BuildSettings buildSettings; 351 if (auto target = key in targets) 352 { 353 value.getPlatformSettings(buildSettings, genSettings.platform, target.pack.path); 354 buildSettings.processVars(m_project, target.pack, buildSettings, genSettings, true); 355 dependencyBuildSettings[key] = buildSettings; 356 } 357 } 358 configureDependencies(*roottarget, targets, dependencyBuildSettings); 359 360 // 2. add Have_dependency_xyz for all direct dependencies of a target 361 // (includes incorporated non-target dependencies and their dependencies) 362 foreach (ref ti; targets.byValue) 363 { 364 import std.range : chain; 365 import dub.internal.utils : stripDlangSpecialChars; 366 367 auto bs = &ti.buildSettings; 368 auto pkgnames = ti.packages.map!(p => p.name).chain(ti.dependencies); 369 bs.addVersions(pkgnames.map!(pn => "Have_" ~ stripDlangSpecialChars(pn)).array); 370 } 371 372 // 3. upwards inherit full build configurations (import paths, versions, debugVersions, versionFilters, importPaths, ...) 373 void configureDependents(ref TargetInfo ti, TargetInfo[string] targets, size_t level = 0) 374 { 375 // use `visited` here as pkgs cannot depend on themselves 376 if (ti.pack in visited) 377 return; 378 visited[ti.pack] = typeof(visited[ti.pack]).init; 379 380 logDiagnostic("%sConfiguring dependent %s, deps:%(%s, %)", ' '.repeat(2 * level), ti.pack.name, ti.dependencies); 381 // embedded non-binary dependencies 382 foreach (deppack; ti.packages[1 .. $]) 383 ti.buildSettings.add(targets[deppack.name].buildSettings); 384 // binary dependencies 385 foreach (depname; ti.dependencies) 386 { 387 auto pdepti = &targets[depname]; 388 configureDependents(*pdepti, targets, level + 1); 389 mergeFromDependency(pdepti.buildSettings, ti.buildSettings, genSettings.platform); 390 } 391 } 392 393 configureDependents(*roottarget, targets); 394 visited.clear(); 395 396 // 4. Filter applicable version and debug version identifiers 397 if (genSettings.filterVersions) 398 { 399 foreach (name, ref ti; targets) 400 { 401 import std.algorithm.sorting : partition; 402 403 auto bs = &ti.buildSettings; 404 405 auto filtered = bs.versions.partition!(v => bs.versionFilters.canFind(v)); 406 logDebug("Filtering out unused versions for %s: %s", name, filtered); 407 bs.versions = bs.versions[0 .. $ - filtered.length]; 408 409 filtered = bs.debugVersions.partition!(v => bs.debugVersionFilters.canFind(v)); 410 logDebug("Filtering out unused debug versions for %s: %s", name, filtered); 411 bs.debugVersions = bs.debugVersions[0 .. $ - filtered.length]; 412 } 413 } 414 415 // 5. override string import files in dependencies 416 static void overrideStringImports(ref TargetInfo target, 417 ref TargetInfo parent, TargetInfo[string] targets, string[] overrides) 418 { 419 // Since string import paths are inherited from dependencies in the 420 // inheritance step above (step 3), it is guaranteed that all 421 // following dependencies will not have string import paths either, 422 // so we can skip the recursion here 423 if (!target.buildSettings.stringImportPaths.length) 424 return; 425 426 // do not use visited here as string imports can be overridden by *any* parent 427 // 428 // special support for overriding string imports in parent packages 429 // this is a candidate for deprecation, once an alternative approach 430 // has been found 431 bool any_override = false; 432 433 // override string import files (used for up to date checking) 434 foreach (ref f; target.buildSettings.stringImportFiles) 435 { 436 foreach (o; overrides) 437 { 438 NativePath op; 439 if (f != o && NativePath(f).head == (op = NativePath(o)).head) { 440 logDebug("string import %s overridden by %s", f, o); 441 f = o; 442 any_override = true; 443 } 444 } 445 } 446 447 // override string import paths by prepending to the list, in 448 // case there is any overlapping file 449 if (any_override) 450 target.buildSettings.prependStringImportPaths(parent.buildSettings.stringImportPaths); 451 452 // add all files to overrides for recursion 453 overrides ~= target.buildSettings.stringImportFiles; 454 455 // recursively override all dependencies with the accumulated files/paths 456 foreach (depname; target.dependencies) 457 overrideStringImports(targets[depname], target, targets, overrides); 458 } 459 460 // push string import paths/files down to all direct and indirect 461 // dependencies, overriding their own 462 foreach (depname; roottarget.dependencies) 463 overrideStringImports(targets[depname], *roottarget, targets, 464 roottarget.buildSettings.stringImportFiles); 465 466 // remove targets without output 467 foreach (name; targets.keys) 468 { 469 if (!hasOutput[name]) 470 targets.remove(name); 471 } 472 } 473 474 // infer applicable version identifiers 475 private static void inferVersionFilters(ref TargetInfo ti) 476 { 477 import std.algorithm.searching : any; 478 import std.file : timeLastModified; 479 import std.path : extension; 480 import std.range : chain; 481 import std.regex : ctRegex, matchAll; 482 import std.stdio : File; 483 import std.datetime : Clock, SysTime, UTC; 484 import dub.compilers.utils : isLinkerFile; 485 import dub.internal.vibecompat.data.json : Json, JSONException; 486 487 auto bs = &ti.buildSettings; 488 489 // only infer if neither version filters are specified explicitly 490 if (bs.versionFilters.length || bs.debugVersionFilters.length) 491 { 492 logDebug("Using specified versionFilters for %s: %s %s", ti.pack.name, 493 bs.versionFilters, bs.debugVersionFilters); 494 return; 495 } 496 497 // check all existing source files for version identifiers 498 static immutable dexts = [".d", ".di"]; 499 auto srcs = chain(bs.sourceFiles, bs.importFiles, bs.stringImportFiles) 500 .filter!(f => dexts.canFind(f.extension)).filter!exists; 501 // try to load cached filters first 502 auto cache = ti.pack.metadataCache; 503 try 504 { 505 auto cachedFilters = cache["versionFilters"]; 506 if (cachedFilters.type != Json.Type.undefined) 507 cachedFilters = cachedFilters[ti.config]; 508 if (cachedFilters.type != Json.Type.undefined) 509 { 510 immutable mtime = SysTime.fromISOExtString(cachedFilters["mtime"].get!string); 511 if (!srcs.any!(src => src.timeLastModified > mtime)) 512 { 513 auto versionFilters = cachedFilters["versions"][].map!(j => j.get!string).array; 514 auto debugVersionFilters = cachedFilters["debugVersions"][].map!(j => j.get!string).array; 515 logDebug("Using cached versionFilters for %s: %s %s", ti.pack.name, 516 versionFilters, debugVersionFilters); 517 bs.addVersionFilters(versionFilters); 518 bs.addDebugVersionFilters(debugVersionFilters); 519 return; 520 } 521 } 522 } 523 catch (JSONException e) 524 { 525 logWarn("Exception during loading invalid package cache %s.\n%s", 526 ti.pack.path ~ ".dub/metadata_cache.json", e); 527 } 528 529 // use ctRegex for performance reasons, only small compile time increase 530 enum verRE = ctRegex!`(?:^|\s)version\s*\(\s*([^\s]*?)\s*\)`; 531 enum debVerRE = ctRegex!`(?:^|\s)debug\s*\(\s*([^\s]*?)\s*\)`; 532 533 auto versionFilters = appender!(string[]); 534 auto debugVersionFilters = appender!(string[]); 535 536 foreach (file; srcs) 537 { 538 foreach (line; File(file).byLine) 539 { 540 foreach (m; line.matchAll(verRE)) 541 if (!versionFilters.data.canFind(m[1])) 542 versionFilters.put(m[1].idup); 543 foreach (m; line.matchAll(debVerRE)) 544 if (!debugVersionFilters.data.canFind(m[1])) 545 debugVersionFilters.put(m[1].idup); 546 } 547 } 548 logDebug("Using inferred versionFilters for %s: %s %s", ti.pack.name, 549 versionFilters.data, debugVersionFilters.data); 550 bs.addVersionFilters(versionFilters.data); 551 bs.addDebugVersionFilters(debugVersionFilters.data); 552 553 auto cachedFilters = cache["versionFilters"]; 554 if (cachedFilters.type == Json.Type.undefined) 555 cachedFilters = cache["versionFilters"] = [ti.config: Json.emptyObject]; 556 cachedFilters[ti.config] = [ 557 "mtime": Json(Clock.currTime(UTC()).toISOExtString), 558 "versions": Json(versionFilters.data.map!Json.array), 559 "debugVersions": Json(debugVersionFilters.data.map!Json.array), 560 ]; 561 ti.pack.metadataCache = cache; 562 } 563 564 private static void mergeFromDependent(const scope ref BuildSettings parent, ref BuildSettings child) 565 { 566 child.addVersions(parent.versions); 567 child.addDebugVersions(parent.debugVersions); 568 child.addOptions(BuildOptions(parent.options & inheritedBuildOptions)); 569 } 570 571 private static void mergeFromDependency(const scope ref BuildSettings child, ref BuildSettings parent, const scope ref BuildPlatform platform) 572 { 573 import dub.compilers.utils : isLinkerFile; 574 575 parent.addDFlags(child.dflags); 576 parent.addVersions(child.versions); 577 parent.addDebugVersions(child.debugVersions); 578 parent.addVersionFilters(child.versionFilters); 579 parent.addDebugVersionFilters(child.debugVersionFilters); 580 parent.addImportPaths(child.importPaths); 581 parent.addStringImportPaths(child.stringImportPaths); 582 // linking of static libraries is done by parent 583 if (child.targetType == TargetType.staticLibrary) { 584 parent.addSourceFiles(child.sourceFiles.filter!(f => isLinkerFile(platform, f)).array); 585 parent.addLibs(child.libs); 586 parent.addLFlags(child.lflags); 587 } 588 } 589 590 // configure targets for build types such as release, or unittest-cov 591 private void addBuildTypeSettings(TargetInfo[string] targets, in GeneratorSettings settings) 592 { 593 foreach (ref ti; targets.byValue) { 594 ti.buildSettings.add(settings.buildSettings); 595 596 // add build type settings and convert plain DFLAGS to build options 597 m_project.addBuildTypeSettings(ti.buildSettings, settings, ti.pack is m_project.rootPackage); 598 settings.compiler.extractBuildOptions(ti.buildSettings); 599 600 auto tt = ti.buildSettings.targetType; 601 enforce (tt != TargetType.sourceLibrary || ti.pack !is m_project.rootPackage || (ti.buildSettings.options & BuildOption.syntaxOnly), 602 format("Main package must not have target type \"%s\". Cannot build.", tt)); 603 } 604 } 605 } 606 607 608 struct GeneratorSettings { 609 BuildPlatform platform; 610 Compiler compiler; 611 string config; 612 string buildType; 613 BuildSettings buildSettings; 614 BuildMode buildMode = BuildMode.separate; 615 int targetExitStatus; 616 617 bool combined; // compile all in one go instead of each dependency separately 618 bool filterVersions; 619 620 // only used for generator "build" 621 bool run, force, direct, rdmd, tempBuild, parallelBuild; 622 623 /// single file dub package 624 bool single; 625 626 string[] runArgs; 627 void delegate(int status, string output) compileCallback; 628 void delegate(int status, string output) linkCallback; 629 void delegate(int status, string output) runCallback; 630 } 631 632 633 /** 634 Determines the mode in which the compiler and linker are invoked. 635 */ 636 enum BuildMode { 637 separate, /// Compile and link separately 638 allAtOnce, /// Perform compile and link with a single compiler invocation 639 singleFile, /// Compile each file separately 640 //multipleObjects, /// Generate an object file per module 641 //multipleObjectsPerModule, /// Use the -multiobj switch to generate multiple object files per module 642 //compileOnly /// Do not invoke the linker (can be done using a post build command) 643 } 644 645 646 /** 647 Creates a project generator of the given type for the specified project. 648 */ 649 ProjectGenerator createProjectGenerator(string generator_type, Project project) 650 { 651 assert(project !is null, "Project instance needed to create a generator."); 652 653 generator_type = generator_type.toLower(); 654 switch(generator_type) { 655 default: 656 throw new Exception("Unknown project generator: "~generator_type); 657 case "build": 658 logDebug("Creating build generator."); 659 return new BuildGenerator(project); 660 case "mono-d": 661 throw new Exception("The Mono-D generator has been removed. Use Mono-D's built in DUB support instead."); 662 case "visuald": 663 logDebug("Creating VisualD generator."); 664 return new VisualDGenerator(project); 665 case "sublimetext": 666 logDebug("Creating SublimeText generator."); 667 return new SublimeTextGenerator(project); 668 case "cmake": 669 logDebug("Creating CMake generator."); 670 return new CMakeGenerator(project); 671 } 672 } 673 674 675 /** 676 Calls delegates on files and directories in the given path that match any globs. 677 */ 678 void findFilesMatchingGlobs(in NativePath path, in string[] globList, void delegate(string file) addFile, void delegate(string dir) addDir) 679 { 680 import std.path : globMatch; 681 682 string[] globs; 683 foreach (f; globList) 684 { 685 if (f.canFind("*", "?") || 686 (f.canFind("{") && f.balancedParens('{', '}')) || 687 (f.canFind("[") && f.balancedParens('[', ']'))) 688 { 689 globs ~= f; 690 } 691 else 692 { 693 if (f.isDir) 694 addDir(f); 695 else 696 addFile(f); 697 } 698 } 699 if (globs.length) // Search all files for glob matches 700 foreach (f; dirEntries(path.toNativeString(), SpanMode.breadth)) 701 foreach (glob; globs) 702 if (f.name().globMatch(glob)) 703 { 704 if (f.isDir) 705 addDir(f); 706 else 707 addFile(f); 708 break; 709 } 710 } 711 712 713 /** 714 Calls delegates on files in the given path that match any globs. 715 716 If a directory matches a glob, the delegate is called on all existing files inside it recursively 717 in depth-first pre-order. 718 */ 719 void findFilesMatchingGlobs(in NativePath path, in string[] globList, void delegate(string file) addFile) 720 { 721 void addDir(string dir) 722 { 723 foreach (f; dirEntries(dir, SpanMode.breadth)) 724 addFile(f); 725 } 726 727 findFilesMatchingGlobs(path, globList, addFile, &addDir); 728 } 729 730 731 /** 732 Runs pre-build commands and performs other required setup before project files are generated. 733 */ 734 private void prepareGeneration(in Package pack, in Project proj, in GeneratorSettings settings, 735 in BuildSettings buildsettings) 736 { 737 if (buildsettings.preGenerateCommands.length && !isRecursiveInvocation(pack.name)) { 738 logInfo("Running pre-generate commands for %s...", pack.name); 739 runBuildCommands(buildsettings.preGenerateCommands, pack, proj, settings, buildsettings); 740 } 741 } 742 743 /** 744 Runs post-build commands and copies required files to the binary directory. 745 */ 746 private void finalizeGeneration(in Package pack, in Project proj, in GeneratorSettings settings, 747 in BuildSettings buildsettings, NativePath target_path, bool generate_binary) 748 { 749 if (buildsettings.postGenerateCommands.length && !isRecursiveInvocation(pack.name)) { 750 logInfo("Running post-generate commands for %s...", pack.name); 751 runBuildCommands(buildsettings.postGenerateCommands, pack, proj, settings, buildsettings); 752 } 753 754 if (generate_binary) { 755 if (!exists(buildsettings.targetPath)) 756 mkdirRecurse(buildsettings.targetPath); 757 758 if (buildsettings.copyFiles.length) { 759 void copyFolderRec(NativePath folder, NativePath dstfolder) 760 { 761 mkdirRecurse(dstfolder.toNativeString()); 762 foreach (de; iterateDirectory(folder.toNativeString())) { 763 if (de.isDirectory) { 764 copyFolderRec(folder ~ de.name, dstfolder ~ de.name); 765 } else { 766 try hardLinkFile(folder ~ de.name, dstfolder ~ de.name, true); 767 catch (Exception e) { 768 logWarn("Failed to copy file %s: %s", (folder ~ de.name).toNativeString(), e.msg); 769 } 770 } 771 } 772 } 773 774 void tryCopyDir(string file) 775 { 776 auto src = NativePath(file); 777 if (!src.absolute) src = pack.path ~ src; 778 auto dst = target_path ~ NativePath(file).head; 779 if (src == dst) { 780 logDiagnostic("Skipping copy of %s (same source and destination)", file); 781 return; 782 } 783 logDiagnostic(" %s to %s", src.toNativeString(), dst.toNativeString()); 784 try { 785 copyFolderRec(src, dst); 786 } catch(Exception e) logWarn("Failed to copy %s to %s: %s", src.toNativeString(), dst.toNativeString(), e.msg); 787 } 788 789 void tryCopyFile(string file) 790 { 791 auto src = NativePath(file); 792 if (!src.absolute) src = pack.path ~ src; 793 auto dst = target_path ~ NativePath(file).head; 794 if (src == dst) { 795 logDiagnostic("Skipping copy of %s (same source and destination)", file); 796 return; 797 } 798 logDiagnostic(" %s to %s", src.toNativeString(), dst.toNativeString()); 799 try { 800 hardLinkFile(src, dst, true); 801 } catch(Exception e) logWarn("Failed to copy %s to %s: %s", src.toNativeString(), dst.toNativeString(), e.msg); 802 } 803 logInfo("Copying files for %s...", pack.name); 804 findFilesMatchingGlobs(pack.path, buildsettings.copyFiles, &tryCopyFile, &tryCopyDir); 805 } 806 807 } 808 } 809 810 811 /** Runs a list of build commands for a particular package. 812 813 This function sets all DUB speficic environment variables and makes sure 814 that recursive dub invocations are detected and don't result in infinite 815 command execution loops. The latter could otherwise happen when a command 816 runs "dub describe" or similar functionality. 817 */ 818 void runBuildCommands(in string[] commands, in Package pack, in Project proj, 819 in GeneratorSettings settings, in BuildSettings build_settings) 820 { 821 import dub.internal.utils : getDUBExePath, runCommands; 822 import std.conv : to, text; 823 import std.process : environment, escapeShellFileName; 824 825 string[string] env = environment.toAA(); 826 // TODO: do more elaborate things here 827 // TODO: escape/quote individual items appropriately 828 env["VERSIONS"] = join(cast(string[])build_settings.versions," "); 829 env["LIBS"] = join(cast(string[])build_settings.libs," "); 830 env["SOURCE_FILES"] = join(cast(string[])build_settings.sourceFiles," "); 831 env["IMPORT_PATHS"] = join(cast(string[])build_settings.importPaths," "); 832 env["STRING_IMPORT_PATHS"] = join(cast(string[])build_settings.stringImportPaths," "); 833 834 env["DC"] = settings.platform.compilerBinary; 835 env["DC_BASE"] = settings.platform.compiler; 836 env["D_FRONTEND_VER"] = to!string(settings.platform.frontendVersion); 837 838 env["DUB_EXE"] = getDUBExePath(settings.platform.compilerBinary); 839 env["DUB_PLATFORM"] = join(cast(string[])settings.platform.platform," "); 840 env["DUB_ARCH"] = join(cast(string[])settings.platform.architecture," "); 841 842 env["DUB_TARGET_TYPE"] = to!string(build_settings.targetType); 843 env["DUB_TARGET_PATH"] = build_settings.targetPath; 844 env["DUB_TARGET_NAME"] = build_settings.targetName; 845 env["DUB_TARGET_EXIT_STATUS"] = settings.targetExitStatus.text; 846 env["DUB_WORKING_DIRECTORY"] = build_settings.workingDirectory; 847 env["DUB_MAIN_SOURCE_FILE"] = build_settings.mainSourceFile; 848 849 env["DUB_CONFIG"] = settings.config; 850 env["DUB_BUILD_TYPE"] = settings.buildType; 851 env["DUB_BUILD_MODE"] = to!string(settings.buildMode); 852 env["DUB_PACKAGE"] = pack.name; 853 env["DUB_PACKAGE_DIR"] = pack.path.toNativeString(); 854 env["DUB_ROOT_PACKAGE"] = proj.rootPackage.name; 855 env["DUB_ROOT_PACKAGE_DIR"] = proj.rootPackage.path.toNativeString(); 856 env["DUB_PACKAGE_VERSION"] = pack.version_.toString(); 857 858 env["DUB_COMBINED"] = settings.combined? "TRUE" : ""; 859 env["DUB_RUN"] = settings.run? "TRUE" : ""; 860 env["DUB_FORCE"] = settings.force? "TRUE" : ""; 861 env["DUB_DIRECT"] = settings.direct? "TRUE" : ""; 862 env["DUB_RDMD"] = settings.rdmd? "TRUE" : ""; 863 env["DUB_TEMP_BUILD"] = settings.tempBuild? "TRUE" : ""; 864 env["DUB_PARALLEL_BUILD"] = settings.parallelBuild? "TRUE" : ""; 865 866 env["DUB_RUN_ARGS"] = (cast(string[])settings.runArgs).map!(escapeShellFileName).join(" "); 867 868 auto cfgs = proj.getPackageConfigs(settings.platform, settings.config, true); 869 auto rootPackageBuildSettings = proj.rootPackage.getBuildSettings(settings.platform, cfgs[proj.rootPackage.name]); 870 env["DUB_ROOT_PACKAGE_TARGET_TYPE"] = to!string(rootPackageBuildSettings.targetType); 871 env["DUB_ROOT_PACKAGE_TARGET_PATH"] = rootPackageBuildSettings.targetPath; 872 env["DUB_ROOT_PACKAGE_TARGET_NAME"] = rootPackageBuildSettings.targetName; 873 874 auto depNames = proj.dependencies.map!((a) => a.name).array(); 875 storeRecursiveInvokations(env, proj.rootPackage.name ~ depNames); 876 runCommands(commands, env, pack.path().toString()); 877 } 878 879 private bool isRecursiveInvocation(string pack) 880 { 881 import std.algorithm : canFind, splitter; 882 import std.process : environment; 883 884 return environment 885 .get("DUB_PACKAGES_USED", "") 886 .splitter(",") 887 .canFind(pack); 888 } 889 890 private void storeRecursiveInvokations(string[string] env, string[] packs) 891 { 892 import std.algorithm : canFind, splitter; 893 import std.range : chain; 894 import std.process : environment; 895 896 env["DUB_PACKAGES_USED"] = environment 897 .get("DUB_PACKAGES_USED", "") 898 .splitter(",") 899 .chain(packs) 900 .join(","); 901 }