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