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 } 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 TargetInfo[string] targets; 93 string[string] configs = m_project.getPackageConfigs(settings.platform, settings.config); 94 95 foreach (pack; m_project.getTopologicalPackageList(true, null, configs)) { 96 BuildSettings buildsettings; 97 buildsettings.processVars(m_project, pack, pack.getBuildSettings(settings.platform, configs[pack.name]), true); 98 prepareGeneration(pack, m_project, settings, buildsettings); 99 } 100 101 string[] mainfiles; 102 collect(settings, m_project.rootPackage, targets, configs, mainfiles, null); 103 downwardsInheritSettings(m_project.rootPackage.name, targets, targets[m_project.rootPackage.name].buildSettings); 104 addBuildTypeSettings(targets, settings); 105 foreach (ref t; targets.byValue) enforceBuildRequirements(t.buildSettings); 106 auto bs = &targets[m_project.rootPackage.name].buildSettings; 107 if (bs.targetType == TargetType.executable) bs.addSourceFiles(mainfiles); 108 109 generateTargets(settings, targets); 110 111 foreach (pack; m_project.getTopologicalPackageList(true, null, configs)) { 112 BuildSettings buildsettings; 113 buildsettings.processVars(m_project, pack, pack.getBuildSettings(settings.platform, configs[pack.name]), true); 114 bool generate_binary = !(buildsettings.options & BuildOption.syntaxOnly); 115 finalizeGeneration(pack, m_project, settings, buildsettings, Path(bs.targetPath), generate_binary); 116 } 117 118 performPostGenerateActions(settings, targets); 119 } 120 121 /** Overridden in derived classes to implement the actual generator functionality. 122 123 The function should go through all targets recursively. The first target 124 (which is guaranteed to be there) is 125 $(D targets[m_project.rootPackage.name]). The recursive descent is then 126 done using the $(D TargetInfo.linkDependencies) list. 127 128 This method is also potentially responsible for running the pre and post 129 build commands, while pre and post generate commands are already taken 130 care of by the $(D generate) method. 131 132 Params: 133 settings = The generator settings used for this run 134 targets = A map from package name to TargetInfo that contains all 135 binary targets to be built. 136 */ 137 protected abstract void generateTargets(GeneratorSettings settings, in TargetInfo[string] targets); 138 139 /** Overridable method to be invoked after the generator process has finished. 140 141 An examples of functionality placed here is to run the application that 142 has just been built. 143 */ 144 protected void performPostGenerateActions(GeneratorSettings settings, in TargetInfo[string] targets) {} 145 146 private BuildSettings collect(GeneratorSettings settings, Package pack, ref TargetInfo[string] targets, in string[string] configs, ref string[] main_files, string bin_pack) 147 { 148 import std.algorithm : sort; 149 import dub.compilers.utils : isLinkerFile; 150 151 if (auto pt = pack.name in targets) return pt.buildSettings; 152 153 // determine the actual target type 154 auto shallowbs = pack.getBuildSettings(settings.platform, configs[pack.name]); 155 TargetType tt = shallowbs.targetType; 156 if (pack is m_project.rootPackage) { 157 if (tt == TargetType.autodetect || tt == TargetType.library) tt = TargetType.staticLibrary; 158 } else { 159 if (tt == TargetType.autodetect || tt == TargetType.library) tt = settings.combined ? TargetType.sourceLibrary : TargetType.staticLibrary; 160 else if (tt == TargetType.dynamicLibrary) { 161 logWarn("Dynamic libraries are not yet supported as dependencies - building as static library."); 162 tt = TargetType.staticLibrary; 163 } 164 } 165 if (tt != TargetType.none && tt != TargetType.sourceLibrary && shallowbs.sourceFiles.empty) { 166 logWarn(`Configuration '%s' of package %s contains no source files. Please add {"targetType": "none"} to it's package description to avoid building it.`, 167 configs[pack.name], pack.name); 168 tt = TargetType.none; 169 } 170 171 shallowbs.targetType = tt; 172 bool generates_binary = tt != TargetType.sourceLibrary && tt != TargetType.none; 173 bool is_target = generates_binary || pack is m_project.rootPackage; 174 175 if (tt == TargetType.none) { 176 // ignore any build settings for targetType none (only dependencies will be processed) 177 shallowbs = BuildSettings.init; 178 shallowbs.targetType = TargetType.none; 179 } 180 181 // start to build up the build settings 182 BuildSettings buildsettings; 183 processVars(buildsettings, m_project, pack, shallowbs, true); 184 185 // remove any mainSourceFile from library builds 186 if (buildsettings.targetType != TargetType.executable && buildsettings.mainSourceFile.length) { 187 buildsettings.sourceFiles = buildsettings.sourceFiles.filter!(f => f != buildsettings.mainSourceFile)().array; 188 main_files ~= buildsettings.mainSourceFile; 189 } 190 191 logDiagnostic("Generate target %s (%s %s %s)", pack.name, buildsettings.targetType, buildsettings.targetPath, buildsettings.targetName); 192 if (is_target) 193 targets[pack.name] = TargetInfo(pack, [pack], configs[pack.name], buildsettings, null); 194 195 auto deps = pack.getDependencies(configs[pack.name]); 196 foreach (depname; deps.keys.sort()) { 197 auto depspec = deps[depname]; 198 auto dep = m_project.getDependency(depname, depspec.optional); 199 if (!dep) continue; 200 201 auto depbs = collect(settings, dep, targets, configs, main_files, is_target ? pack.name : bin_pack); 202 203 if (depbs.targetType != TargetType.sourceLibrary && depbs.targetType != TargetType.none) { 204 // add a reference to the target binary and remove all source files in the dependency build settings 205 depbs.sourceFiles = depbs.sourceFiles.filter!(f => f.isLinkerFile()).array; 206 depbs.importFiles = null; 207 } 208 209 buildsettings.add(depbs); 210 211 if (depbs.targetType == TargetType.executable) 212 continue; 213 214 auto pt = (is_target ? pack.name : bin_pack) in targets; 215 assert(pt !is null); 216 if (auto pdt = depname in targets) { 217 pt.dependencies ~= depname; 218 pt.linkDependencies ~= depname; 219 if (depbs.targetType == TargetType.staticLibrary) 220 pt.linkDependencies = pt.linkDependencies.filter!(d => !pdt.linkDependencies.canFind(d)).array ~ pdt.linkDependencies; 221 } else pt.packages ~= dep; 222 } 223 224 if (is_target) targets[pack.name].buildSettings = buildsettings.dup; 225 226 return buildsettings; 227 } 228 229 private string[] downwardsInheritSettings(string target, TargetInfo[string] targets, in BuildSettings root_settings) 230 { 231 import dub.internal.utils : stripDlangSpecialChars; 232 233 auto ti = &targets[target]; 234 ti.buildSettings.addVersions(root_settings.versions); 235 ti.buildSettings.addDebugVersions(root_settings.debugVersions); 236 ti.buildSettings.addOptions(BuildOptions(cast(BuildOptions)root_settings.options & inheritedBuildOptions)); 237 238 // special support for overriding string imports in parent packages 239 // this is a candidate for deprecation, once an alternative approach 240 // has been found 241 if (ti.buildSettings.stringImportPaths.length) { 242 // override string import files (used for up to date checking) 243 foreach (ref f; ti.buildSettings.stringImportFiles) 244 foreach (fi; root_settings.stringImportFiles) 245 if (f != fi && Path(f).head == Path(fi).head) { 246 f = fi; 247 } 248 249 // add the string import paths (used by the compiler to find the overridden files) 250 ti.buildSettings.prependStringImportPaths(root_settings.stringImportPaths); 251 } 252 253 string[] packs = ti.packages.map!(p => p.name).array; 254 foreach (d; ti.dependencies) 255 packs ~= downwardsInheritSettings(d, targets, root_settings); 256 257 logDebug("%s: %s", target, packs); 258 259 // Add Have_* versions *after* downwards inheritance, so that dependencies 260 // are build independently of the parent packages w.r.t the other parent 261 // dependencies. This enables sharing of the same package build for 262 // multiple dependees. 263 ti.buildSettings.addVersions(packs.map!(pn => "Have_" ~ stripDlangSpecialChars(pn)).array); 264 265 return packs; 266 } 267 268 private void addBuildTypeSettings(TargetInfo[string] targets, GeneratorSettings settings) 269 { 270 foreach (ref t; targets) { 271 t.buildSettings.add(settings.buildSettings); 272 273 // add build type settings and convert plain DFLAGS to build options 274 m_project.addBuildTypeSettings(t.buildSettings, settings.platform, settings.buildType, t.pack is m_project.rootPackage); 275 settings.compiler.extractBuildOptions(t.buildSettings); 276 277 auto tt = t.buildSettings.targetType; 278 bool generates_binary = tt != TargetType.sourceLibrary && tt != TargetType.none; 279 enforce (generates_binary || t.pack !is m_project.rootPackage || (t.buildSettings.options & BuildOption.syntaxOnly), 280 format("Main package must have a binary target type, not %s. Cannot build.", tt)); 281 } 282 } 283 } 284 285 286 struct GeneratorSettings { 287 BuildPlatform platform; 288 Compiler compiler; 289 string config; 290 string buildType; 291 BuildSettings buildSettings; 292 BuildMode buildMode = BuildMode.separate; 293 294 bool combined; // compile all in one go instead of each dependency separately 295 296 // only used for generator "build" 297 bool run, force, direct, clean, rdmd, tempBuild, parallelBuild; 298 string[] runArgs; 299 void delegate(int status, string output) compileCallback; 300 void delegate(int status, string output) linkCallback; 301 void delegate(int status, string output) runCallback; 302 } 303 304 305 /** 306 Determines the mode in which the compiler and linker are invoked. 307 */ 308 enum BuildMode { 309 separate, /// Compile and link separately 310 allAtOnce, /// Perform compile and link with a single compiler invocation 311 singleFile, /// Compile each file separately 312 //multipleObjects, /// Generate an object file per module 313 //multipleObjectsPerModule, /// Use the -multiobj switch to generate multiple object files per module 314 //compileOnly /// Do not invoke the linker (can be done using a post build command) 315 } 316 317 318 /** 319 Creates a project generator of the given type for the specified project. 320 */ 321 ProjectGenerator createProjectGenerator(string generator_type, Project project) 322 { 323 assert(project !is null, "Project instance needed to create a generator."); 324 325 generator_type = generator_type.toLower(); 326 switch(generator_type) { 327 default: 328 throw new Exception("Unknown project generator: "~generator_type); 329 case "build": 330 logDebug("Creating build generator."); 331 return new BuildGenerator(project); 332 case "mono-d": 333 throw new Exception("The Mono-D generator has been removed. Use Mono-D's built in DUB support instead."); 334 case "visuald": 335 logDebug("Creating VisualD generator."); 336 return new VisualDGenerator(project); 337 case "sublimetext": 338 logDebug("Creating SublimeText generator."); 339 return new SublimeTextGenerator(project); 340 case "cmake": 341 logDebug("Creating CMake generator."); 342 return new CMakeGenerator(project); 343 } 344 } 345 346 347 /** 348 Runs pre-build commands and performs other required setup before project files are generated. 349 */ 350 private void prepareGeneration(in Package pack, in Project proj, in GeneratorSettings settings, 351 in BuildSettings buildsettings) 352 { 353 if (buildsettings.preGenerateCommands.length && !isRecursiveInvocation(pack.name)) { 354 logInfo("Running pre-generate commands for %s...", pack.name); 355 runBuildCommands(buildsettings.preGenerateCommands, pack, proj, settings, buildsettings); 356 } 357 } 358 359 /** 360 Runs post-build commands and copies required files to the binary directory. 361 */ 362 private void finalizeGeneration(in Package pack, in Project proj, in GeneratorSettings settings, 363 in BuildSettings buildsettings, Path target_path, bool generate_binary) 364 { 365 import std.path : globMatch; 366 367 if (buildsettings.postGenerateCommands.length && !isRecursiveInvocation(pack.name)) { 368 logInfo("Running post-generate commands for %s...", pack.name); 369 runBuildCommands(buildsettings.postGenerateCommands, pack, proj, settings, buildsettings); 370 } 371 372 if (generate_binary) { 373 if (!exists(buildsettings.targetPath)) 374 mkdirRecurse(buildsettings.targetPath); 375 376 if (buildsettings.copyFiles.length) { 377 void copyFolderRec(Path folder, Path dstfolder) 378 { 379 mkdirRecurse(dstfolder.toNativeString()); 380 foreach (de; iterateDirectory(folder.toNativeString())) { 381 if (de.isDirectory) { 382 copyFolderRec(folder ~ de.name, dstfolder ~ de.name); 383 } else { 384 try hardLinkFile(folder ~ de.name, dstfolder ~ de.name, true); 385 catch (Exception e) { 386 logWarn("Failed to copy file %s: %s", (folder ~ de.name).toNativeString(), e.msg); 387 } 388 } 389 } 390 } 391 392 void tryCopyDir(string file) 393 { 394 auto src = Path(file); 395 if (!src.absolute) src = pack.path ~ src; 396 auto dst = target_path ~ Path(file).head; 397 if (src == dst) { 398 logDiagnostic("Skipping copy of %s (same source and destination)", file); 399 return; 400 } 401 logDiagnostic(" %s to %s", src.toNativeString(), dst.toNativeString()); 402 try { 403 copyFolderRec(src, dst); 404 } catch(Exception e) logWarn("Failed to copy %s to %s: %s", src.toNativeString(), dst.toNativeString(), e.msg); 405 } 406 407 void tryCopyFile(string file) 408 { 409 auto src = Path(file); 410 if (!src.absolute) src = pack.path ~ src; 411 auto dst = target_path ~ Path(file).head; 412 if (src == dst) { 413 logDiagnostic("Skipping copy of %s (same source and destination)", file); 414 return; 415 } 416 logDiagnostic(" %s to %s", src.toNativeString(), dst.toNativeString()); 417 try { 418 hardLinkFile(src, dst, true); 419 } catch(Exception e) logWarn("Failed to copy %s to %s: %s", src.toNativeString(), dst.toNativeString(), e.msg); 420 } 421 logInfo("Copying files for %s...", pack.name); 422 string[] globs; 423 foreach (f; buildsettings.copyFiles) 424 { 425 if (f.canFind("*", "?") || 426 (f.canFind("{") && f.balancedParens('{', '}')) || 427 (f.canFind("[") && f.balancedParens('[', ']'))) 428 { 429 globs ~= f; 430 } 431 else 432 { 433 if (f.isDir) 434 tryCopyDir(f); 435 else 436 tryCopyFile(f); 437 } 438 } 439 if (globs.length) // Search all files for glob matches 440 { 441 foreach (f; dirEntries(pack.path.toNativeString(), SpanMode.breadth)) 442 { 443 foreach (glob; globs) 444 { 445 if (f.name().globMatch(glob)) 446 { 447 if (f.isDir) 448 tryCopyDir(f); 449 else 450 tryCopyFile(f); 451 break; 452 } 453 } 454 } 455 } 456 } 457 458 } 459 } 460 461 462 /** Runs a list of build commands for a particular package. 463 464 This funtion sets all DUB speficic environment variables and makes sure 465 that recursive dub invocations are detected and don't result in infinite 466 command execution loops. The latter could otherwise happen when a command 467 runs "dub describe" or similar functionality. 468 */ 469 void runBuildCommands(in string[] commands, in Package pack, in Project proj, 470 in GeneratorSettings settings, in BuildSettings build_settings) 471 { 472 import std.conv; 473 import std.process; 474 import dub.internal.utils; 475 476 string[string] env = environment.toAA(); 477 // TODO: do more elaborate things here 478 // TODO: escape/quote individual items appropriately 479 env["DFLAGS"] = join(cast(string[])build_settings.dflags, " "); 480 env["LFLAGS"] = join(cast(string[])build_settings.lflags," "); 481 env["VERSIONS"] = join(cast(string[])build_settings.versions," "); 482 env["LIBS"] = join(cast(string[])build_settings.libs," "); 483 env["IMPORT_PATHS"] = join(cast(string[])build_settings.importPaths," "); 484 env["STRING_IMPORT_PATHS"] = join(cast(string[])build_settings.stringImportPaths," "); 485 486 env["DC"] = settings.platform.compilerBinary; 487 env["DC_BASE"] = settings.platform.compiler; 488 env["D_FRONTEND_VER"] = to!string(settings.platform.frontendVersion); 489 490 env["DUB_PLATFORM"] = join(cast(string[])settings.platform.platform," "); 491 env["DUB_ARCH"] = join(cast(string[])settings.platform.architecture," "); 492 493 env["DUB_TARGET_TYPE"] = to!string(build_settings.targetType); 494 env["DUB_TARGET_PATH"] = build_settings.targetPath; 495 env["DUB_TARGET_NAME"] = build_settings.targetName; 496 env["DUB_WORKING_DIRECTORY"] = build_settings.workingDirectory; 497 env["DUB_MAIN_SOURCE_FILE"] = build_settings.mainSourceFile; 498 499 env["DUB_CONFIG"] = settings.config; 500 env["DUB_BUILD_TYPE"] = settings.buildType; 501 env["DUB_BUILD_MODE"] = to!string(settings.buildMode); 502 env["DUB_PACKAGE"] = pack.name; 503 env["DUB_PACKAGE_DIR"] = pack.path.toNativeString(); 504 env["DUB_ROOT_PACKAGE"] = proj.rootPackage.name; 505 env["DUB_ROOT_PACKAGE_DIR"] = proj.rootPackage.path.toNativeString(); 506 507 env["DUB_COMBINED"] = settings.combined? "TRUE" : ""; 508 env["DUB_RUN"] = settings.run? "TRUE" : ""; 509 env["DUB_FORCE"] = settings.force? "TRUE" : ""; 510 env["DUB_DIRECT"] = settings.direct? "TRUE" : ""; 511 env["DUB_CLEAN"] = settings.clean? "TRUE" : ""; 512 env["DUB_RDMD"] = settings.rdmd? "TRUE" : ""; 513 env["DUB_TEMP_BUILD"] = settings.tempBuild? "TRUE" : ""; 514 env["DUB_PARALLEL_BUILD"] = settings.parallelBuild? "TRUE" : ""; 515 516 env["DUB_RUN_ARGS"] = (cast(string[])settings.runArgs).map!(escapeShellFileName).join(" "); 517 518 auto depNames = proj.dependencies.map!((a) => a.name).array(); 519 storeRecursiveInvokations(env, proj.rootPackage.name ~ depNames); 520 runCommands(commands, env); 521 } 522 523 private bool isRecursiveInvocation(string pack) 524 { 525 import std.algorithm : canFind, splitter; 526 import std.process : environment; 527 528 return environment 529 .get("DUB_PACKAGES_USED", "") 530 .splitter(",") 531 .canFind(pack); 532 } 533 534 private void storeRecursiveInvokations(string[string] env, string[] packs) 535 { 536 import std.algorithm : canFind, splitter; 537 import std.range : chain; 538 import std.process : environment; 539 540 env["DUB_PACKAGES_USED"] = environment 541 .get("DUB_PACKAGES_USED", "") 542 .splitter(",") 543 .chain(packs) 544 .join(","); 545 }