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