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