1 /** 2 Stuff with dependencies. 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.package_; 9 10 import dub.compilers.compiler; 11 import dub.dependency; 12 import dub.internal.utils; 13 import dub.internal.vibecompat.core.log; 14 import dub.internal.vibecompat.core.file; 15 import dub.internal.vibecompat.data.json; 16 import dub.internal.vibecompat.inet.url; 17 18 import std.algorithm; 19 import std.array; 20 import std.conv; 21 import std.exception; 22 import std.file; 23 import std.range; 24 import std.string; 25 import std.traits : EnumMembers; 26 27 enum PackageJsonFilename = "package.json"; 28 29 30 /// Representing a downloded / cached package, usually constructed from a json object. 31 /// Documentation of the package.json can be found at 32 /// http://registry.vibed.org/package-format 33 class Package { 34 static struct LocalPackageDef { string name; Version version_; Path path; } 35 36 private { 37 Path m_path; 38 PackageInfo m_info; 39 Package m_parentPackage; 40 Package[] m_subPackages; 41 } 42 43 this(Path root, Package parent = null) 44 { 45 this(jsonFromFile(root ~ PackageJsonFilename), root, parent); 46 } 47 48 this(Json packageInfo, Path root = Path(), Package parent = null) 49 { 50 m_parentPackage = parent; 51 m_path = root; 52 53 // check for default string import folders 54 foreach(defvf; ["views"]){ 55 auto p = m_path ~ defvf; 56 if( existsFile(p) ) 57 m_info.buildSettings.stringImportPaths[""] ~= defvf; 58 } 59 60 string app_main_file, lib_main_file; 61 auto pkg_name = packageInfo.name.get!string(); 62 63 // check for default source folders 64 foreach(defsf; ["source/", "src/"]){ 65 auto p = m_path ~ defsf; 66 if( existsFile(p) ){ 67 m_info.buildSettings.sourcePaths[""] ~= defsf; 68 m_info.buildSettings.importPaths[""] ~= defsf; 69 foreach (fil; ["app.d", "main.d", pkg_name ~ "/main.d", pkg_name ~ "/" ~ pkg_name ~ ".d"]) 70 if (existsFile(p ~ fil)) { 71 app_main_file = Path(defsf ~ fil).toNativeString(); 72 break; 73 } 74 foreach (fil; [pkg_name ~ "/all.d", pkg_name ~ "/package.d", pkg_name ~ "/" ~ pkg_name ~ ".d"]) 75 if (existsFile(p ~ fil)) { 76 lib_main_file = Path(defsf ~ fil).toNativeString(); 77 break; 78 } 79 } 80 } 81 82 // parse the JSON description 83 { 84 scope(failure) logError("Failed to parse package description in %s", root.toNativeString()); 85 m_info.parseJson(packageInfo); 86 87 // try to run git to determine the version of the package if no explicit version was given 88 if (m_info.version_.length == 0 && !parent) { 89 import std.process; 90 try { 91 auto branch = execute(["git", "--git-dir="~(root~".git").toNativeString(), "rev-parse", "--abbrev-ref", "HEAD"]); 92 enforce(branch.status == 0, "git rev-parse failed: " ~ branch.output); 93 if (branch.output.strip() == "HEAD") { 94 //auto ver = execute("git",) 95 enforce(false, "oops"); 96 } else { 97 m_info.version_ = "~" ~ branch.output.strip(); 98 } 99 } catch (Exception e) { 100 logDebug("Failed to run git: %s", e.msg); 101 } 102 103 if (m_info.version_.length == 0) { 104 logDiagnostic("Note: Failed to determine version of package %s at %s. Assuming ~master.", m_info.name, this.path.toNativeString()); 105 m_info.version_ = "~master"; 106 } else logDiagnostic("Determined package version using GIT: %s %s", m_info.name, m_info.version_); 107 } 108 } 109 110 // generate default configurations if none are defined 111 if (m_info.configurations.length == 0) { 112 if (m_info.buildSettings.targetType == TargetType.executable) { 113 BuildSettingsTemplate app_settings; 114 app_settings.targetType = TargetType.executable; 115 if (m_info.buildSettings.mainSourceFile.empty) app_settings.mainSourceFile = app_main_file; 116 m_info.configurations ~= ConfigurationInfo("application", app_settings); 117 } else if (m_info.buildSettings.targetType != TargetType.none) { 118 BuildSettingsTemplate lib_settings; 119 lib_settings.targetType = m_info.buildSettings.targetType == TargetType.autodetect ? TargetType.library : m_info.buildSettings.targetType; 120 121 if (m_info.buildSettings.targetType == TargetType.autodetect) { 122 if (app_main_file.length && lib_main_file != app_main_file) { 123 lib_settings.excludedSourceFiles[""] ~= app_main_file; 124 125 BuildSettingsTemplate app_settings; 126 app_settings.targetType = TargetType.executable; 127 app_settings.mainSourceFile = app_main_file; 128 m_info.configurations ~= ConfigurationInfo("application", app_settings); 129 } 130 } 131 132 if (m_info.buildSettings.mainSourceFile.empty) lib_settings.mainSourceFile = lib_main_file; 133 m_info.configurations ~= ConfigurationInfo("library", lib_settings); 134 } 135 } 136 137 // load all sub packages defined in the package description 138 foreach (p; packageInfo.subPackages.opt!(Json[])) 139 m_subPackages ~= new Package(p, root, this); 140 } 141 142 @property string name() 143 const { 144 if (m_parentPackage) return m_parentPackage.name ~ ":" ~ m_info.name; 145 else return m_info.name; 146 } 147 @property string vers() const { return m_parentPackage ? m_parentPackage.vers : m_info.version_; } 148 @property Version ver() const { return Version(this.vers); } 149 @property ref inout(PackageInfo) info() inout { return m_info; } 150 @property Path path() const { return m_path; } 151 @property Path packageInfoFile() const { return m_path ~ "package.json"; } 152 @property const(Dependency[string]) dependencies() const { return m_info.dependencies; } 153 @property inout(Package) basePackage() inout { return m_parentPackage ? m_parentPackage.basePackage : this; } 154 @property inout(Package) parentPackage() inout { return m_parentPackage; } 155 @property inout(Package)[] subPackages() inout { return m_subPackages; } 156 157 @property string[] configurations() 158 const { 159 auto ret = appender!(string[])(); 160 foreach( ref config; m_info.configurations ) 161 ret.put(config.name); 162 return ret.data; 163 } 164 165 inout(Package) getSubPackage(string name) inout { 166 foreach (p; m_subPackages) 167 if (p.name == this.name ~ ":" ~ name) 168 return p; 169 throw new Exception(format("Unknown sub package: %s:%s", this.name, name)); 170 } 171 172 void warnOnSpecialCompilerFlags() 173 { 174 // warn about use of special flags 175 m_info.buildSettings.warnOnSpecialCompilerFlags(m_info.name, null); 176 foreach (ref config; m_info.configurations) 177 config.buildSettings.warnOnSpecialCompilerFlags(m_info.name, config.name); 178 } 179 180 /// Returns all BuildSettings for the given platform and config. 181 BuildSettings getBuildSettings(in BuildPlatform platform, string config) 182 const { 183 BuildSettings ret; 184 m_info.buildSettings.getPlatformSettings(ret, platform, this.path); 185 bool found = false; 186 foreach(ref conf; m_info.configurations){ 187 if( conf.name != config ) continue; 188 conf.buildSettings.getPlatformSettings(ret, platform, this.path); 189 found = true; 190 break; 191 } 192 assert(found || config is null, "Unknown configuration for "~m_info.name~": "~config); 193 194 // construct default target name based on package name 195 if( ret.targetName.empty ) ret.targetName = this.name.replace(":", "_"); 196 197 // special support for DMD style flags 198 getCompiler("dmd").extractBuildOptions(ret); 199 200 return ret; 201 } 202 203 void addBuildTypeSettings(ref BuildSettings settings, in BuildPlatform platform, string build_type) 204 const { 205 if (build_type == "$DFLAGS") { 206 import std.process; 207 string dflags = environment.get("DFLAGS"); 208 settings.addDFlags(dflags.split()); 209 return; 210 } 211 212 if (auto pbt = build_type in m_info.buildTypes) { 213 logDiagnostic("Using custom build type '%s'.", build_type); 214 pbt.getPlatformSettings(settings, platform, this.path); 215 } else { 216 with(BuildOptions) switch (build_type) { 217 default: throw new Exception(format("Unknown build type for %s: '%s'", this.name, build_type)); 218 case "plain": break; 219 case "debug": settings.addOptions(debugMode, debugInfo); break; 220 case "release": settings.addOptions(releaseMode, optimize, inline); break; 221 case "unittest": settings.addOptions(unittests, debugMode, debugInfo); break; 222 case "docs": settings.addOptions(syntaxOnly); settings.addDFlags("-c", "-Dddocs"); break; 223 case "ddox": settings.addOptions(syntaxOnly); settings.addDFlags("-c", "-Df__dummy.html", "-Xfdocs.json"); break; 224 case "profile": settings.addOptions(profile, optimize, inline, debugInfo); break; 225 case "cov": settings.addOptions(coverage, debugInfo); break; 226 case "unittest-cov": settings.addOptions(unittests, coverage, debugMode, debugInfo); break; 227 } 228 } 229 } 230 231 string getSubConfiguration(string config, in Package dependency, in BuildPlatform platform) 232 const { 233 bool found = false; 234 foreach(ref c; m_info.configurations){ 235 if( c.name == config ){ 236 if( auto pv = dependency.name in c.buildSettings.subConfigurations ) return *pv; 237 found = true; 238 break; 239 } 240 } 241 assert(found || config is null, "Invalid configuration \""~config~"\" for "~this.name); 242 if( auto pv = dependency.name in m_info.buildSettings.subConfigurations ) return *pv; 243 return null; 244 } 245 246 /// Returns the default configuration to build for the given platform 247 string getDefaultConfiguration(in BuildPlatform platform, bool allow_non_library = false) 248 const { 249 foreach (ref conf; m_info.configurations) { 250 if (!conf.matchesPlatform(platform)) continue; 251 if (!allow_non_library && conf.buildSettings.targetType == TargetType.executable) continue; 252 return conf.name; 253 } 254 return null; 255 } 256 257 /// Returns a list of configurations suitable for the given platform 258 string[] getPlatformConfigurations(in BuildPlatform platform, bool is_main_package = false) 259 const { 260 auto ret = appender!(string[]); 261 foreach(ref conf; m_info.configurations){ 262 if (!conf.matchesPlatform(platform)) continue; 263 if (!is_main_package && conf.buildSettings.targetType == TargetType.executable) continue; 264 ret ~= conf.name; 265 } 266 if (ret.data.length == 0) ret.put(null); 267 return ret.data; 268 } 269 270 /// Human readable information of this package and its dependencies. 271 string generateInfoString() const { 272 string s; 273 s ~= m_info.name ~ ", version '" ~ m_info.version_ ~ "'"; 274 s ~= "\n Dependencies:"; 275 foreach(string p, ref const Dependency v; m_info.dependencies) 276 s ~= "\n " ~ p ~ ", version '" ~ to!string(v) ~ "'"; 277 return s; 278 } 279 280 /// Writes the json file back to the filesystem 281 void writeJson(Path path) { 282 auto dstFile = openFile((path~PackageJsonFilename).toString(), FileMode.CreateTrunc); 283 scope(exit) dstFile.close(); 284 dstFile.writePrettyJsonString(m_info.toJson()); 285 assert(false); 286 } 287 288 bool hasDependency(string depname, string config) 289 const { 290 if (depname in m_info.buildSettings.dependencies) return true; 291 foreach (ref c; m_info.configurations) 292 if ((config.empty || c.name == config) && depname in c.buildSettings.dependencies) 293 return true; 294 return false; 295 } 296 297 void describe(ref Json dst, BuildPlatform platform, string config) 298 { 299 dst.path = m_path.toNativeString(); 300 dst.name = this.name; 301 dst["version"] = this.vers; 302 dst.description = m_info.description; 303 dst.homepage = m_info.homepage; 304 dst.authors = m_info.authors.serializeToJson(); 305 dst.copyright = m_info.copyright; 306 dst.license = m_info.license; 307 dst.dependencies = m_info.dependencies.keys.serializeToJson(); 308 309 // save build settings 310 BuildSettings bs = getBuildSettings(platform, config); 311 312 foreach (string k, v; bs.serializeToJson()) dst[k] = v; 313 dst.remove("requirements"); 314 dst.remove("sourceFiles"); 315 dst.remove("importFiles"); 316 dst.remove("stringImportFiles"); 317 dst.targetType = bs.targetType.to!string(); 318 if (dst.targetType != TargetType.none) 319 dst.targetFileName = getTargetFileName(bs, platform); 320 321 // prettify build requirements output 322 Json[] breqs; 323 for (int i = 1; i <= BuildRequirements.max; i <<= 1) 324 if (bs.requirements & i) 325 breqs ~= Json(to!string(cast(BuildRequirements)i)); 326 dst.buildRequirements = breqs; 327 328 // prettify options output 329 Json[] bopts; 330 for (int i = 1; i <= BuildOptions.max; i <<= 1) 331 if (bs.options & i) 332 bopts ~= Json(to!string(cast(BuildOptions)i)); 333 dst.options = bopts; 334 335 // prettify files output 336 Json[] files; 337 foreach (f; bs.sourceFiles) { 338 auto jf = Json.emptyObject; 339 jf.path = f; 340 jf["type"] = "source"; 341 files ~= jf; 342 } 343 foreach (f; bs.importFiles) { 344 auto jf = Json.emptyObject; 345 jf.path = f; 346 jf["type"] = "import"; 347 files ~= jf; 348 } 349 foreach (f; bs.stringImportFiles) { 350 auto jf = Json.emptyObject; 351 jf.path = f; 352 jf["type"] = "stringImport"; 353 files ~= jf; 354 } 355 dst.files = Json(files); 356 } 357 } 358 359 /// Specifying package information without any connection to a certain 360 /// retrived package, like Package class is doing. 361 struct PackageInfo { 362 string name; 363 string version_; 364 string description; 365 string homepage; 366 string[] authors; 367 string copyright; 368 string license; 369 string[] ddoxFilterArgs; 370 BuildSettingsTemplate buildSettings; 371 ConfigurationInfo[] configurations; 372 BuildSettingsTemplate[string] buildTypes; 373 374 @property const(Dependency)[string] dependencies() 375 const { 376 const(Dependency)[string] ret; 377 foreach (n, d; this.buildSettings.dependencies) 378 ret[n] = d; 379 foreach (ref c; configurations) 380 foreach (n, d; c.buildSettings.dependencies) 381 ret[n] = d; 382 return ret; 383 } 384 385 inout(ConfigurationInfo) getConfiguration(string name) 386 inout { 387 foreach (c; configurations) 388 if (c.name == name) 389 return c; 390 throw new Exception("Unknown configuration: "~name); 391 } 392 393 void parseJson(Json json) 394 { 395 foreach( string field, value; json ){ 396 switch(field){ 397 default: break; 398 case "name": this.name = value.get!string; break; 399 case "version": this.version_ = value.get!string; break; 400 case "description": this.description = value.get!string; break; 401 case "homepage": this.homepage = value.get!string; break; 402 case "authors": this.authors = deserializeJson!(string[])(value); break; 403 case "copyright": this.copyright = value.get!string; break; 404 case "license": this.license = value.get!string; break; 405 case "-ddoxFilterArgs": this.ddoxFilterArgs = deserializeJson!(string[])(value); break; 406 case "configurations": break; // handled below, after the global settings have been parsed 407 case "buildTypes": 408 foreach (string name, settings; value) { 409 BuildSettingsTemplate bs; 410 bs.parseJson(settings); 411 buildTypes[name] = bs; 412 } 413 break; 414 } 415 } 416 417 // parse build settings 418 this.buildSettings.parseJson(json); 419 420 if (auto pv = "configurations" in json) { 421 TargetType deftargettp = TargetType.library; 422 if (this.buildSettings.targetType != TargetType.autodetect) 423 deftargettp = this.buildSettings.targetType; 424 425 foreach (settings; *pv) { 426 ConfigurationInfo ci; 427 ci.parseJson(settings, deftargettp); 428 this.configurations ~= ci; 429 } 430 } 431 432 enforce(this.name.length > 0, "The package \"name\" field is missing or empty."); 433 } 434 435 Json toJson() 436 const { 437 auto ret = buildSettings.toJson(); 438 ret.name = this.name; 439 if( !this.version_.empty ) ret["version"] = this.version_; 440 if( !this.description.empty ) ret.description = this.description; 441 if( !this.homepage.empty ) ret.homepage = this.homepage; 442 if( !this.authors.empty ) ret.authors = serializeToJson(this.authors); 443 if( !this.copyright.empty ) ret.copyright = this.copyright; 444 if( !this.license.empty ) ret.license = this.license; 445 if( !this.ddoxFilterArgs.empty ) ret["-ddoxFilterArgs"] = this.ddoxFilterArgs.serializeToJson(); 446 if( this.configurations ){ 447 Json[] configs; 448 foreach(config; this.configurations) 449 configs ~= config.toJson(); 450 ret.configurations = configs; 451 } 452 return ret; 453 } 454 } 455 456 /// Bundles information about a build configuration. 457 struct ConfigurationInfo { 458 string name; 459 string[] platforms; 460 BuildSettingsTemplate buildSettings; 461 462 this(string name, BuildSettingsTemplate build_settings) 463 { 464 enforce(!name.empty, "Configuration name is empty."); 465 this.name = name; 466 this.buildSettings = build_settings; 467 } 468 469 void parseJson(Json json, TargetType default_target_type = TargetType.library) 470 { 471 this.buildSettings.targetType = default_target_type; 472 473 foreach(string name, value; json){ 474 switch(name){ 475 default: break; 476 case "name": 477 this.name = value.get!string(); 478 enforce(!this.name.empty, "Configurations must have a non-empty name."); 479 break; 480 case "platforms": this.platforms = deserializeJson!(string[])(value); break; 481 } 482 } 483 484 enforce(!this.name.empty, "Configuration is missing a name."); 485 486 BuildSettingsTemplate bs; 487 this.buildSettings.parseJson(json); 488 } 489 490 Json toJson() 491 const { 492 auto ret = buildSettings.toJson(); 493 ret.name = name; 494 if( this.platforms.length ) ret.platforms = serializeToJson(platforms); 495 return ret; 496 } 497 498 bool matchesPlatform(in BuildPlatform platform) 499 const { 500 if( platforms.empty ) return true; 501 foreach(p; platforms) 502 if( platform.matchesSpecification("-"~p) ) 503 return true; 504 return false; 505 } 506 } 507 508 /// This keeps general information about how to build a package. 509 /// It contains functions to create a specific BuildSetting, targeted at 510 /// a certain BuildPlatform. 511 struct BuildSettingsTemplate { 512 Dependency[string] dependencies; 513 TargetType targetType = TargetType.autodetect; 514 string targetPath; 515 string targetName; 516 string workingDirectory; 517 string mainSourceFile; 518 string[string] subConfigurations; 519 string[][string] dflags; 520 string[][string] lflags; 521 string[][string] libs; 522 string[][string] sourceFiles; 523 string[][string] sourcePaths; 524 string[][string] excludedSourceFiles; 525 string[][string] copyFiles; 526 string[][string] versions; 527 string[][string] debugVersions; 528 string[][string] importPaths; 529 string[][string] stringImportPaths; 530 string[][string] preGenerateCommands; 531 string[][string] postGenerateCommands; 532 string[][string] preBuildCommands; 533 string[][string] postBuildCommands; 534 BuildRequirements[string] buildRequirements; 535 BuildOptions[string] buildOptions; 536 537 void parseJson(Json json) 538 { 539 foreach(string name, value; json) 540 { 541 auto idx = std..string.indexOf(name, "-"); 542 string basename, suffix; 543 if( idx >= 0 ) basename = name[0 .. idx], suffix = name[idx .. $]; 544 else basename = name; 545 switch(basename){ 546 default: break; 547 case "dependencies": 548 foreach( string pkg, verspec; value ) { 549 enforce(pkg !in this.dependencies, "The dependency '"~pkg~"' is specified more than once." ); 550 Dependency dep; 551 if( verspec.type == Json.Type.object ){ 552 enforce("version" in verspec, "Package information provided for package " ~ pkg ~ " is missing a version field."); 553 auto ver = verspec["version"].get!string; 554 if( auto pp = "path" in verspec ) { 555 // This enforces the "version" specifier to be a simple version, 556 // without additional range specifiers. 557 dep = Dependency(Version(ver)); 558 dep.path = Path(verspec.path.get!string()); 559 } else { 560 // Using the string to be able to specifiy a range of versions. 561 dep = Dependency(ver); 562 } 563 if( auto po = "optional" in verspec ) { 564 dep.optional = verspec.optional.get!bool(); 565 } 566 } else { 567 // canonical "package-id": "version" 568 dep = Dependency(verspec.get!string()); 569 } 570 this.dependencies[pkg] = dep; 571 } 572 break; 573 case "targetType": 574 enforce(suffix.empty, "targetType does not support platform customization."); 575 targetType = value.get!string().to!TargetType(); 576 break; 577 case "targetPath": 578 enforce(suffix.empty, "targetPath does not support platform customization."); 579 this.targetPath = value.get!string; 580 if (this.workingDirectory is null) this.workingDirectory = this.targetPath; 581 break; 582 case "targetName": 583 enforce(suffix.empty, "targetName does not support platform customization."); 584 this.targetName = value.get!string; 585 break; 586 case "workingDirectory": 587 enforce(suffix.empty, "workingDirectory does not support platform customization."); 588 this.workingDirectory = value.get!string; 589 break; 590 case "mainSourceFile": 591 enforce(suffix.empty, "mainSourceFile does not support platform customization."); 592 this.mainSourceFile = value.get!string; 593 break; 594 case "subConfigurations": 595 enforce(suffix.empty, "subConfigurations does not support platform customization."); 596 this.subConfigurations = deserializeJson!(string[string])(value); 597 break; 598 case "dflags": this.dflags[suffix] = deserializeJson!(string[])(value); break; 599 case "lflags": this.lflags[suffix] = deserializeJson!(string[])(value); break; 600 case "libs": this.libs[suffix] = deserializeJson!(string[])(value); break; 601 case "files": 602 case "sourceFiles": this.sourceFiles[suffix] = deserializeJson!(string[])(value); break; 603 case "sourcePaths": this.sourcePaths[suffix] = deserializeJson!(string[])(value); break; 604 case "sourcePath": this.sourcePaths[suffix] ~= [value.get!string()]; break; // deprecated 605 case "excludedSourceFiles": this.excludedSourceFiles[suffix] = deserializeJson!(string[])(value); break; 606 case "copyFiles": this.copyFiles[suffix] = deserializeJson!(string[])(value); break; 607 case "versions": this.versions[suffix] = deserializeJson!(string[])(value); break; 608 case "debugVersions": this.debugVersions[suffix] = deserializeJson!(string[])(value); break; 609 case "importPaths": this.importPaths[suffix] = deserializeJson!(string[])(value); break; 610 case "stringImportPaths": this.stringImportPaths[suffix] = deserializeJson!(string[])(value); break; 611 case "preGenerateCommands": this.preGenerateCommands[suffix] = deserializeJson!(string[])(value); break; 612 case "postGenerateCommands": this.postGenerateCommands[suffix] = deserializeJson!(string[])(value); break; 613 case "preBuildCommands": this.preBuildCommands[suffix] = deserializeJson!(string[])(value); break; 614 case "postBuildCommands": this.postBuildCommands[suffix] = deserializeJson!(string[])(value); break; 615 case "buildRequirements": 616 BuildRequirements reqs; 617 foreach (req; deserializeJson!(string[])(value)) 618 reqs |= to!BuildRequirements(req); 619 this.buildRequirements[suffix] = reqs; 620 break; 621 case "buildOptions": 622 BuildOptions options; 623 foreach (opt; deserializeJson!(string[])(value)) 624 options |= to!BuildOptions(opt); 625 this.buildOptions[suffix] = options; 626 break; 627 } 628 } 629 } 630 631 Json toJson() 632 const { 633 auto ret = Json.emptyObject; 634 if( this.dependencies !is null ){ 635 auto deps = Json.emptyObject; 636 foreach( pack, d; this.dependencies ){ 637 if( d.path.empty && !d.optional ){ 638 deps[pack] = d.toString(); 639 } else { 640 auto vjson = Json.emptyObject; 641 vjson["version"] = d.version_.toString(); 642 if (!d.path.empty) vjson["path"] = d.path.toString(); 643 if (d.optional) vjson["optional"] = true; 644 deps[pack] = vjson; 645 } 646 } 647 ret.dependencies = deps; 648 } 649 if (targetType != TargetType.autodetect) ret["targetType"] = targetType.to!string(); 650 if (!targetPath.empty) ret["targetPath"] = targetPath; 651 if (!targetName.empty) ret["targetName"] = targetName; 652 if (!workingDirectory.empty) ret["workingDirectory"] = workingDirectory; 653 if (!mainSourceFile.empty) ret["mainSourceFile"] = mainSourceFile; 654 foreach (suffix, arr; dflags) ret["dflags"~suffix] = serializeToJson(arr); 655 foreach (suffix, arr; lflags) ret["lflags"~suffix] = serializeToJson(arr); 656 foreach (suffix, arr; libs) ret["libs"~suffix] = serializeToJson(arr); 657 foreach (suffix, arr; sourceFiles) ret["sourceFiles"~suffix] = serializeToJson(arr); 658 foreach (suffix, arr; sourcePaths) ret["sourcePaths"~suffix] = serializeToJson(arr); 659 foreach (suffix, arr; excludedSourceFiles) ret["excludedSourceFiles"~suffix] = serializeToJson(arr); 660 foreach (suffix, arr; copyFiles) ret["copyFiles"~suffix] = serializeToJson(arr); 661 foreach (suffix, arr; versions) ret["versions"~suffix] = serializeToJson(arr); 662 foreach (suffix, arr; debugVersions) ret["debugVersions"~suffix] = serializeToJson(arr); 663 foreach (suffix, arr; importPaths) ret["importPaths"~suffix] = serializeToJson(arr); 664 foreach (suffix, arr; stringImportPaths) ret["stringImportPaths"~suffix] = serializeToJson(arr); 665 foreach (suffix, arr; preGenerateCommands) ret["preGenerateCommands"~suffix] = serializeToJson(arr); 666 foreach (suffix, arr; postGenerateCommands) ret["postGenerateCommands"~suffix] = serializeToJson(arr); 667 foreach (suffix, arr; preBuildCommands) ret["preBuildCommands"~suffix] = serializeToJson(arr); 668 foreach (suffix, arr; postBuildCommands) ret["postBuildCommands"~suffix] = serializeToJson(arr); 669 foreach (suffix, arr; buildRequirements) { 670 string[] val; 671 foreach (i; [EnumMembers!BuildRequirements]) 672 if (arr & i) val ~= to!string(i); 673 ret["buildRequirements"~suffix] = serializeToJson(val); 674 } 675 foreach (suffix, arr; buildOptions) { 676 string[] val; 677 foreach (i; [EnumMembers!BuildOptions]) 678 if (arr & i) val ~= to!string(i); 679 ret["buildOptions"~suffix] = serializeToJson(val); 680 } 681 return ret; 682 } 683 684 /// Constructs a BuildSettings object from this template. 685 void getPlatformSettings(ref BuildSettings dst, in BuildPlatform platform, Path base_path) 686 const { 687 dst.targetType = this.targetType; 688 if (!this.targetPath.empty) dst.targetPath = this.targetPath; 689 if (!this.targetName.empty) dst.targetName = this.targetName; 690 if (!this.workingDirectory.empty) dst.workingDirectory = this.workingDirectory; 691 if (!this.mainSourceFile.empty) dst.mainSourceFile = this.mainSourceFile; 692 693 void collectFiles(string method)(in string[][string] paths_map, string pattern) 694 { 695 foreach (suffix, paths; paths_map) { 696 if (!platform.matchesSpecification(suffix)) 697 continue; 698 699 foreach (spath; paths) { 700 enforce(!spath.empty, "Paths must not be empty strings."); 701 auto path = Path(spath); 702 if (!path.absolute) path = base_path ~ path; 703 if (!existsFile(path) || !isDir(path.toNativeString())) { 704 logWarn("Invalid source/import path: %s", path.toNativeString()); 705 continue; 706 } 707 708 foreach (d; dirEntries(path.toNativeString(), pattern, SpanMode.depth)) { 709 if (isDir(d.name)) continue; 710 auto src = Path(d.name).relativeTo(base_path); 711 __traits(getMember, dst, method)(src.toNativeString()); 712 } 713 } 714 } 715 } 716 717 // collect files from all source/import folders 718 collectFiles!"addSourceFiles"(sourcePaths, "*.d"); 719 collectFiles!"addImportFiles"(importPaths, "*.{d,di}"); 720 dst.removeImportFiles(dst.sourceFiles); 721 collectFiles!"addStringImportFiles"(stringImportPaths, "*"); 722 723 getPlatformSetting!("dflags", "addDFlags")(dst, platform); 724 getPlatformSetting!("lflags", "addLFlags")(dst, platform); 725 getPlatformSetting!("libs", "addLibs")(dst, platform); 726 getPlatformSetting!("sourceFiles", "addSourceFiles")(dst, platform); 727 getPlatformSetting!("excludedSourceFiles", "removeSourceFiles")(dst, platform); 728 getPlatformSetting!("copyFiles", "addCopyFiles")(dst, platform); 729 getPlatformSetting!("versions", "addVersions")(dst, platform); 730 getPlatformSetting!("debugVersions", "addDebugVersions")(dst, platform); 731 getPlatformSetting!("importPaths", "addImportPaths")(dst, platform); 732 getPlatformSetting!("stringImportPaths", "addStringImportPaths")(dst, platform); 733 getPlatformSetting!("preGenerateCommands", "addPreGenerateCommands")(dst, platform); 734 getPlatformSetting!("postGenerateCommands", "addPostGenerateCommands")(dst, platform); 735 getPlatformSetting!("preBuildCommands", "addPreBuildCommands")(dst, platform); 736 getPlatformSetting!("postBuildCommands", "addPostBuildCommands")(dst, platform); 737 getPlatformSetting!("buildRequirements", "addRequirements")(dst, platform); 738 getPlatformSetting!("buildOptions", "addOptions")(dst, platform); 739 } 740 741 void getPlatformSetting(string name, string addname)(ref BuildSettings dst, in BuildPlatform platform) 742 const { 743 foreach(suffix, values; __traits(getMember, this, name)){ 744 if( platform.matchesSpecification(suffix) ) 745 __traits(getMember, dst, addname)(values); 746 } 747 } 748 749 void warnOnSpecialCompilerFlags(string package_name, string config_name) 750 { 751 auto nodef = false; 752 auto noprop = false; 753 foreach (req; this.buildRequirements) { 754 if (req & BuildRequirements.noDefaultFlags) nodef = true; 755 if (req & BuildRequirements.relaxProperties) noprop = true; 756 } 757 758 if (noprop) { 759 logWarn(`Warning: "buildRequirements": ["relaxProperties"] is deprecated and is now the default behavior. Note that the -property switch will probably be removed in future versions of DMD.`); 760 logWarn(""); 761 } 762 763 if (nodef) { 764 logWarn("Warning: This package uses the \"noDefaultFlags\" build requirement. Please use only for development purposes and not for released packages."); 765 logWarn(""); 766 } else { 767 string[] all_dflags; 768 foreach (flags; this.dflags) 769 all_dflags ~= flags; 770 .warnOnSpecialCompilerFlags(all_dflags, package_name, config_name); 771 } 772 } 773 } 774 775 /// Returns all package names, starting with the root package in [0]. 776 string[] getSubPackagePath(string package_name) 777 { 778 return package_name.split(":"); 779 } 780 781 /// Returns the name of the base package in the case of some sub package or the 782 /// package itself, if it is already a full package. 783 string getBasePackage(string package_name) 784 { 785 return package_name.getSubPackagePath()[0]; 786 }