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