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