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