1 /** 2 Representing a full project, with a root Package and several 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, Sönke Ludwig 7 */ 8 module dub.project; 9 10 import dub.compilers.compiler; 11 import dub.dependency; 12 import dub.description; 13 import dub.internal.utils; 14 import dub.internal.vibecompat.core.file; 15 import dub.internal.vibecompat.core.log; 16 import dub.internal.vibecompat.data.json; 17 import dub.internal.vibecompat.inet.url; 18 import dub.package_; 19 import dub.packagemanager; 20 import dub.packagesupplier; 21 import dub.generators.generator; 22 23 24 // todo: cleanup imports. 25 import std.algorithm; 26 import std.array; 27 import std.conv; 28 import std.datetime; 29 import std.exception; 30 import std.file; 31 import std.process; 32 import std..string; 33 import std.typecons; 34 import std.zip; 35 import std.encoding : sanitize; 36 37 /// Representing a full project, with a root Package and several dependencies. 38 class Project { 39 private { 40 PackageManager m_packageManager; 41 Json m_packageSettings; 42 Package m_rootPackage; 43 Package[] m_dependencies; 44 Package[][Package] m_dependees; 45 SelectedVersions m_selections; 46 } 47 48 this(PackageManager package_manager, Path project_path) 49 { 50 Package pack; 51 auto packageFile = Package.findPackageFile(project_path); 52 if (packageFile.empty) { 53 logWarn("There was no package description found for the application in '%s'.", project_path.toNativeString()); 54 pack = new Package(null, project_path); 55 } else { 56 pack = package_manager.getOrLoadPackage(project_path, packageFile); 57 } 58 59 this(package_manager, pack); 60 } 61 62 this(PackageManager package_manager, Package pack) 63 { 64 m_packageManager = package_manager; 65 m_rootPackage = pack; 66 m_packageSettings = Json.emptyObject; 67 68 try m_packageSettings = jsonFromFile(m_rootPackage.path ~ ".dub/dub.json", true); 69 catch(Exception t) logDiagnostic("Failed to read .dub/dub.json: %s", t.msg); 70 71 auto selverfile = m_rootPackage.path ~ SelectedVersions.defaultFile; 72 if (existsFile(selverfile)) { 73 try m_selections = new SelectedVersions(selverfile); 74 catch(Exception e) { 75 logDiagnostic("A " ~ SelectedVersions.defaultFile ~ " file was not found or failed to load:\n%s", e.msg); 76 m_selections = new SelectedVersions; 77 } 78 } else m_selections = new SelectedVersions; 79 80 reinit(); 81 } 82 83 /// Gathers information 84 @property string info() 85 const { 86 if(!m_rootPackage) 87 return "-Unrecognized application in '"~m_rootPackage.path.toNativeString()~"' (probably no dub.json in this directory)"; 88 string s = "-Application identifier: " ~ m_rootPackage.name; 89 s ~= "\n" ~ m_rootPackage.generateInfoString(); 90 s ~= "\n-Retrieved dependencies:"; 91 foreach(p; m_dependencies) 92 s ~= "\n" ~ p.generateInfoString(); 93 return s; 94 } 95 96 /// Gets all retrieved packages as a "packageId" = "version" associative array 97 @property string[string] cachedPackagesIDs() const { 98 string[string] pkgs; 99 foreach(p; m_dependencies) 100 pkgs[p.name] = p.vers; 101 return pkgs; 102 } 103 104 /// List of retrieved dependency Packages 105 @property const(Package[]) dependencies() const { return m_dependencies; } 106 107 /// Main package. 108 @property inout(Package) rootPackage() inout { return m_rootPackage; } 109 110 /// The versions to use for all dependencies. Call reinit() after changing these. 111 @property inout(SelectedVersions) selections() inout { return m_selections; } 112 113 /// Package manager instance used by the project. 114 @property inout(PackageManager) packageManager() inout { return m_packageManager; } 115 116 /** Allows iteration of the dependency tree in topological order 117 */ 118 int delegate(int delegate(ref const Package)) getTopologicalPackageList(bool children_first = false, in Package root_package = null, string[string] configs = null) 119 const { 120 const(Package) rootpack = root_package ? root_package : m_rootPackage; 121 122 int iterator(int delegate(ref const Package) del) 123 { 124 int ret = 0; 125 bool[const(Package)] visited; 126 void perform_rec(in Package p){ 127 if( p in visited ) return; 128 visited[p] = true; 129 130 if( !children_first ){ 131 ret = del(p); 132 if( ret ) return; 133 } 134 135 auto cfg = configs.get(p.name, null); 136 137 foreach (dn, dv; p.dependencies) { 138 // filter out dependencies not in the current configuration set 139 if (!p.hasDependency(dn, cfg)) continue; 140 auto dependency = getDependency(dn, dv.optional); 141 if(dependency) perform_rec(dependency); 142 if( ret ) return; 143 } 144 145 if( children_first ){ 146 ret = del(p); 147 if( ret ) return; 148 } 149 } 150 perform_rec(rootpack); 151 return ret; 152 } 153 154 return &iterator; 155 } 156 157 inout(Package) getDependency(string name, bool isOptional) 158 inout { 159 foreach(dp; m_dependencies) 160 if( dp.name == name ) 161 return dp; 162 if(!isOptional) throw new Exception("Unknown dependency: "~name); 163 else return null; 164 } 165 166 string getDefaultConfiguration(BuildPlatform platform, bool allow_non_library_configs = true) 167 const { 168 auto cfgs = getPackageConfigs(platform, null, allow_non_library_configs); 169 return cfgs[m_rootPackage.name]; 170 } 171 172 void validate() 173 { 174 // some basic package lint 175 m_rootPackage.warnOnSpecialCompilerFlags(); 176 if (m_rootPackage.name != m_rootPackage.name.toLower()) { 177 logWarn(`WARNING: DUB package names should always be lower case, please change ` 178 ~ `to {"name": "%s"}. You can use {"targetName": "%s"} to keep the current ` 179 ~ `executable name.`, 180 m_rootPackage.name.toLower(), m_rootPackage.name); 181 } else if (!m_rootPackage.info.name.all!(ch => ch >= 'a' && ch <= 'z' || ch >= '0' && ch <= '9' || ch == '-' || ch == '_')) { 182 logWarn(`WARNING: DUB package names may only contain alphanumeric characters, ` 183 ~ `as well as '-' and '_', please modify the "name" field in %s ` 184 ~ `accordingly. You can use {"targetName": "%s"} to keep the current ` 185 ~ `executable name.`, 186 m_rootPackage.packageInfoFilename.toNativeString(), m_rootPackage.name); 187 } 188 enforce(!m_rootPackage.name.canFind(' '), "Aborting due to the package name containing spaces."); 189 190 foreach (dn, ds; m_rootPackage.dependencies) 191 if (ds.isExactVersion && ds.version_.isBranch) { 192 logWarn("WARNING: A deprecated branch based version specification is used " 193 ~ "for the dependency %s. Please use numbered versions instead. Also " 194 ~ "note that you can still use the %s file to override a certain " 195 ~ "dependency to use a branch instead.", 196 dn, SelectedVersions.defaultFile); 197 } 198 199 bool[string] visited; 200 void validateDependenciesRec(Package pack) { 201 foreach (name, vspec_; pack.dependencies) { 202 if (name in visited) continue; 203 visited[name] = true; 204 205 auto basename = getBasePackageName(name); 206 if (m_selections.hasSelectedVersion(basename)) { 207 auto selver = m_selections.getSelectedVersion(basename); 208 if (vspec_.merge(selver) == Dependency.invalid) { 209 logWarn("Selected package %s %s does not match the dependency specification %s in package %s. Need to \"dub upgrade\"?", 210 basename, selver, vspec_, pack.name); 211 } 212 } 213 214 auto deppack = getDependency(name, true); 215 if (deppack) validateDependenciesRec(deppack); 216 } 217 } 218 validateDependenciesRec(m_rootPackage); 219 } 220 221 /// Rereads the applications state. 222 void reinit() 223 { 224 m_dependencies = null; 225 m_packageManager.refresh(false); 226 227 void collectDependenciesRec(Package pack) 228 { 229 logDebug("Collecting dependencies for %s", pack.name); 230 foreach (name, vspec_; pack.dependencies) { 231 Dependency vspec = vspec_; 232 Package p; 233 if (!vspec.path.empty) { 234 Path path = vspec.path; 235 if (!path.absolute) path = pack.path ~ path; 236 logDiagnostic("Adding local %s", path); 237 p = m_packageManager.getOrLoadPackage(path); 238 if (name.canFind(':')) p = m_packageManager.getSubPackage(p, getSubPackageName(name), false); 239 enforce(p.name == name, 240 format("Path based dependency %s is referenced with a wrong name: %s vs. %s", 241 path.toNativeString(), name, p.name)); 242 } 243 244 if (!p) { 245 auto basename = getBasePackageName(name); 246 if (name == m_rootPackage.basePackage.name) { 247 vspec = Dependency(m_rootPackage.ver); 248 p = m_rootPackage.basePackage; 249 } else if (basename == m_rootPackage.basePackage.name) { 250 vspec = Dependency(m_rootPackage.ver); 251 try p = m_packageManager.getSubPackage(m_rootPackage.basePackage, getSubPackageName(name), false); 252 catch (Exception e) { 253 logDiagnostic("Error getting sub package %s: %s", name, e.msg); 254 continue; 255 } 256 } else if (m_selections.hasSelectedVersion(basename)) { 257 vspec = m_selections.getSelectedVersion(basename); 258 p = m_packageManager.getBestPackage(name, vspec); 259 } else if (m_dependencies.canFind!(d => getBasePackageName(d.name) == basename)) { 260 auto idx = m_dependencies.countUntil!(d => getBasePackageName(d.name) == basename); 261 auto bp = m_dependencies[idx].basePackage; 262 vspec = Dependency(bp.path); 263 p = m_packageManager.getSubPackage(bp, getSubPackageName(name), false); 264 } else { 265 logDiagnostic("Version selection for dependency %s (%s) of %s is missing.", 266 basename, name, pack.name); 267 continue; 268 } 269 } 270 271 if (!p) { 272 logDiagnostic("Missing dependency %s %s of %s", name, vspec, pack.name); 273 continue; 274 } 275 276 if (!m_dependencies.canFind(p)) { 277 logDiagnostic("Found dependency %s %s", name, vspec.toString()); 278 m_dependencies ~= p; 279 p.warnOnSpecialCompilerFlags(); 280 collectDependenciesRec(p); 281 } 282 283 m_dependees[p] ~= pack; 284 //enforce(p !is null, "Failed to resolve dependency "~name~" "~vspec.toString()); 285 } 286 } 287 collectDependenciesRec(m_rootPackage); 288 } 289 290 /// Returns the applications name. 291 @property string name() const { return m_rootPackage ? m_rootPackage.name : "app"; } 292 293 @property string[] configurations() const { return m_rootPackage.configurations; } 294 295 /// Returns a map with the configuration for all packages in the dependency tree. 296 string[string] getPackageConfigs(in BuildPlatform platform, string config, bool allow_non_library = true) 297 const { 298 struct Vertex { string pack, config; } 299 struct Edge { size_t from, to; } 300 301 Vertex[] configs; 302 Edge[] edges; 303 string[][string] parents; 304 parents[m_rootPackage.name] = null; 305 foreach (p; getTopologicalPackageList()) 306 foreach (d; p.dependencies.byKey) 307 parents[d] ~= p.name; 308 309 310 size_t createConfig(string pack, string config) { 311 foreach (i, v; configs) 312 if (v.pack == pack && v.config == config) 313 return i; 314 logDebug("Add config %s %s", pack, config); 315 configs ~= Vertex(pack, config); 316 return configs.length-1; 317 } 318 319 bool haveConfig(string pack, string config) { 320 return configs.any!(c => c.pack == pack && c.config == config); 321 } 322 323 size_t createEdge(size_t from, size_t to) { 324 auto idx = edges.countUntil(Edge(from, to)); 325 if (idx >= 0) return idx; 326 logDebug("Including %s %s -> %s %s", configs[from].pack, configs[from].config, configs[to].pack, configs[to].config); 327 edges ~= Edge(from, to); 328 return edges.length-1; 329 } 330 331 void removeConfig(size_t i) { 332 logDebug("Eliminating config %s for %s", configs[i].config, configs[i].pack); 333 configs = configs.remove(i); 334 edges = edges.filter!(e => e.from != i && e.to != i).array(); 335 foreach (ref e; edges) { 336 if (e.from > i) e.from--; 337 if (e.to > i) e.to--; 338 } 339 } 340 341 bool isReachable(string pack, string conf) { 342 if (pack == configs[0].pack && configs[0].config == conf) return true; 343 foreach (e; edges) 344 if (configs[e.to].pack == pack && configs[e.to].config == conf) 345 return true; 346 return false; 347 //return (pack == configs[0].pack && conf == configs[0].config) || edges.canFind!(e => configs[e.to].pack == pack && configs[e.to].config == config); 348 } 349 350 bool isReachableByAllParentPacks(size_t cidx) { 351 bool[string] r; 352 foreach (p; parents[configs[cidx].pack]) r[p] = false; 353 foreach (e; edges) { 354 if (e.to != cidx) continue; 355 if (auto pp = configs[e.from].pack in r) *pp = true; 356 } 357 foreach (bool v; r) if (!v) return false; 358 return true; 359 } 360 361 string[] allconfigs_path; 362 // create a graph of all possible package configurations (package, config) -> (subpackage, subconfig) 363 void determineAllConfigs(in Package p) 364 { 365 auto idx = allconfigs_path.countUntil(p.name); 366 enforce(idx < 0, format("Detected dependency cycle: %s", (allconfigs_path[idx .. $] ~ p.name).join("->"))); 367 allconfigs_path ~= p.name; 368 scope (exit) allconfigs_path.length--; 369 370 // first, add all dependency configurations 371 foreach (dn; p.dependencies.byKey) { 372 auto dp = getDependency(dn, true); 373 if (!dp) continue; 374 determineAllConfigs(dp); 375 } 376 377 // for each configuration, determine the configurations usable for the dependencies 378 outer: foreach (c; p.getPlatformConfigurations(platform, p is m_rootPackage && allow_non_library)) { 379 string[][string] depconfigs; 380 foreach (dn; p.dependencies.byKey) { 381 auto dp = getDependency(dn, true); 382 if (!dp) continue; 383 384 string[] cfgs; 385 auto subconf = p.getSubConfiguration(c, dp, platform); 386 if (!subconf.empty) cfgs = [subconf]; 387 else cfgs = dp.getPlatformConfigurations(platform); 388 cfgs = cfgs.filter!(c => haveConfig(dn, c)).array; 389 390 // if no valid configuration was found for a dependency, don't include the 391 // current configuration 392 if (!cfgs.length) { 393 logDebug("Skip %s %s (missing configuration for %s)", p.name, c, dp.name); 394 continue outer; 395 } 396 depconfigs[dn] = cfgs; 397 } 398 399 // add this configuration to the graph 400 size_t cidx = createConfig(p.name, c); 401 foreach (dn; p.dependencies.byKey) 402 foreach (sc; depconfigs.get(dn, null)) 403 createEdge(cidx, createConfig(dn, sc)); 404 } 405 } 406 if (config.length) createConfig(m_rootPackage.name, config); 407 determineAllConfigs(m_rootPackage); 408 409 // successively remove configurations until only one configuration per package is left 410 bool changed; 411 do { 412 // remove all configs that are not reachable by all parent packages 413 changed = false; 414 for (size_t i = 0; i < configs.length; ) { 415 if (!isReachableByAllParentPacks(i)) { 416 logDebug("NOT REACHABLE by (%s):", parents[configs[i].pack]); 417 removeConfig(i); 418 changed = true; 419 } else i++; 420 } 421 422 // when all edges are cleaned up, pick one package and remove all but one config 423 if (!changed) { 424 foreach (p; getTopologicalPackageList()) { 425 size_t cnt = 0; 426 for (size_t i = 0; i < configs.length; ) { 427 if (configs[i].pack == p.name) { 428 if (++cnt > 1) { 429 logDebug("NON-PRIMARY:"); 430 removeConfig(i); 431 } else i++; 432 } else i++; 433 } 434 if (cnt > 1) { 435 changed = true; 436 break; 437 } 438 } 439 } 440 } while (changed); 441 442 // print out the resulting tree 443 foreach (e; edges) logDebug(" %s %s -> %s %s", configs[e.from].pack, configs[e.from].config, configs[e.to].pack, configs[e.to].config); 444 445 // return the resulting configuration set as an AA 446 string[string] ret; 447 foreach (c; configs) { 448 assert(ret.get(c.pack, c.config) == c.config, format("Conflicting configurations for %s found: %s vs. %s", c.pack, c.config, ret[c.pack])); 449 logDebug("Using configuration '%s' for %s", c.config, c.pack); 450 ret[c.pack] = c.config; 451 } 452 453 // check for conflicts (packages missing in the final configuration graph) 454 void checkPacksRec(in Package pack) { 455 auto pc = pack.name in ret; 456 enforce(pc !is null, "Could not resolve configuration for package "~pack.name); 457 foreach (p, dep; pack.getDependencies(*pc)) { 458 auto deppack = getDependency(p, dep.optional); 459 if (deppack) checkPacksRec(deppack); 460 } 461 } 462 checkPacksRec(m_rootPackage); 463 464 return ret; 465 } 466 467 /** 468 * Fills dst with values from this project. 469 * 470 * dst gets initialized according to the given platform and config. 471 * 472 * Params: 473 * dst = The BuildSettings struct to fill with data. 474 * platform = The platform to retrieve the values for. 475 * config = Values of the given configuration will be retrieved. 476 * root_package = If non null, use it instead of the project's real root package. 477 * shallow = If true, collects only build settings for the main package (including inherited settings) and doesn't stop on target type none and sourceLibrary. 478 */ 479 void addBuildSettings(ref BuildSettings dst, in BuildPlatform platform, string config, in Package root_package = null, bool shallow = false) 480 const { 481 auto configs = getPackageConfigs(platform, config); 482 483 foreach (pkg; this.getTopologicalPackageList(false, root_package, configs)) { 484 auto pkg_path = pkg.path.toNativeString(); 485 dst.addVersions(["Have_" ~ stripDlangSpecialChars(pkg.name)]); 486 487 assert(pkg.name in configs, "Missing configuration for "~pkg.name); 488 logDebug("Gathering build settings for %s (%s)", pkg.name, configs[pkg.name]); 489 490 auto psettings = pkg.getBuildSettings(platform, configs[pkg.name]); 491 if (psettings.targetType != TargetType.none) { 492 if (shallow && pkg !is m_rootPackage) 493 psettings.sourceFiles = null; 494 processVars(dst, this, pkg, psettings); 495 if (psettings.importPaths.empty) 496 logWarn(`Package %s (configuration "%s") defines no import paths, use {"importPaths": [...]} or the default package directory structure to fix this.`, pkg.name, configs[pkg.name]); 497 if (psettings.mainSourceFile.empty && pkg is m_rootPackage && psettings.targetType == TargetType.executable) 498 logWarn(`Executable configuration "%s" of package %s defines no main source file, this may cause certain build modes to fail. Add an explicit "mainSourceFile" to the package description to fix this.`, configs[pkg.name], pkg.name); 499 } 500 if (pkg is m_rootPackage) { 501 if (!shallow) { 502 enforce(psettings.targetType != TargetType.none, "Main package has target type \"none\" - stopping build."); 503 enforce(psettings.targetType != TargetType.sourceLibrary, "Main package has target type \"sourceLibrary\" which generates no target - stopping build."); 504 } 505 dst.targetType = psettings.targetType; 506 dst.targetPath = psettings.targetPath; 507 dst.targetName = psettings.targetName; 508 if (!psettings.workingDirectory.empty) 509 dst.workingDirectory = processVars(psettings.workingDirectory, this, pkg, true); 510 if (psettings.mainSourceFile.length) 511 dst.mainSourceFile = processVars(psettings.mainSourceFile, this, pkg, true); 512 } 513 } 514 515 // always add all version identifiers of all packages 516 foreach (pkg; this.getTopologicalPackageList(false, null, configs)) { 517 auto psettings = pkg.getBuildSettings(platform, configs[pkg.name]); 518 dst.addVersions(psettings.versions); 519 } 520 } 521 522 void addBuildTypeSettings(ref BuildSettings dst, in BuildPlatform platform, string build_type) 523 { 524 bool usedefflags = !(dst.requirements & BuildRequirement.noDefaultFlags); 525 if (usedefflags) { 526 BuildSettings btsettings; 527 m_rootPackage.addBuildTypeSettings(btsettings, platform, build_type); 528 processVars(dst, this, m_rootPackage, btsettings); 529 } 530 } 531 532 /// Determines if the given dependency is already indirectly referenced by other dependencies of pack. 533 bool isRedundantDependency(in Package pack, in Package dependency) 534 const { 535 foreach (dep; pack.dependencies.byKey) { 536 auto dp = getDependency(dep, true); 537 if (!dp) continue; 538 if (dp is dependency) continue; 539 foreach (ddp; getTopologicalPackageList(false, dp)) 540 if (ddp is dependency) return true; 541 } 542 return false; 543 } 544 545 /*bool iterateDependencies(bool delegate(Package pack, string dep_name, Dependency dep_spec) del) 546 { 547 bool all_found = true; 548 549 bool[string] visited; 550 void iterate(Package pack) 551 { 552 if (pack.name in visited) return; 553 visited[pack.name] = true; 554 555 foreach (dn, ds; pack.dependencies) { 556 auto dep = del(pack, dn, ds); 557 if (dep) iterateDependencies(dep); 558 else all_found = false; 559 } 560 } 561 562 return all_found; 563 }*/ 564 565 /// Outputs a build description of the project, including its dependencies. 566 ProjectDescription describe(BuildPlatform platform, string config, string build_type = null) 567 { 568 import dub.generators.targetdescription; 569 570 // store basic build parameters 571 ProjectDescription ret; 572 ret.rootPackage = m_rootPackage.name; 573 ret.configuration = config; 574 ret.buildType = build_type; 575 ret.compiler = platform.compiler; 576 ret.architecture = platform.architecture; 577 ret.platform = platform.platform; 578 579 // collect high level information about projects (useful for IDE display) 580 auto configs = getPackageConfigs(platform, config); 581 ret.packages ~= m_rootPackage.describe(platform, config); 582 foreach (dep; m_dependencies) 583 ret.packages ~= dep.describe(platform, configs[dep.name]); 584 585 if (build_type.length) { 586 // collect build target information (useful for build tools) 587 GeneratorSettings settings; 588 settings.platform = platform; 589 settings.compiler = getCompiler(platform.compilerBinary); 590 settings.config = config; 591 settings.buildType = build_type; 592 auto gen = new TargetDescriptionGenerator(this); 593 try { 594 gen.generate(settings); 595 ret.targets = gen.targetDescriptions; 596 ret.targetLookup = gen.targetDescriptionLookup; 597 } catch (Exception e) { 598 logDiagnostic("Skipping targets description: %s", e.msg); 599 logDebug("Full error: %s", e.toString().sanitize); 600 } 601 } 602 603 return ret; 604 } 605 /// ditto 606 deprecated void describe(ref Json dst, BuildPlatform platform, string config) 607 { 608 auto desc = describe(platform, config); 609 foreach (string key, value; desc.serializeToJson()) 610 dst[key] = value; 611 } 612 613 private string[] listBuildSetting(string attributeName)(BuildPlatform platform, 614 string config, ProjectDescription projectDescription, Compiler compiler, bool disableEscaping) 615 { 616 return listBuildSetting!attributeName(platform, getPackageConfigs(platform, config), 617 projectDescription, compiler, disableEscaping); 618 } 619 620 private string[] listBuildSetting(string attributeName)(BuildPlatform platform, 621 string[string] configs, ProjectDescription projectDescription, Compiler compiler, bool disableEscaping) 622 { 623 if (compiler) 624 return formatBuildSettingCompiler!attributeName(platform, configs, projectDescription, compiler, disableEscaping); 625 else 626 return formatBuildSettingPlain!attributeName(platform, configs, projectDescription); 627 } 628 629 // Output a build setting formatted for a compiler 630 private string[] formatBuildSettingCompiler(string attributeName)(BuildPlatform platform, 631 string[string] configs, ProjectDescription projectDescription, Compiler compiler, bool disableEscaping) 632 { 633 import std.process : escapeShellFileName; 634 import std.path : dirSeparator; 635 636 assert(compiler); 637 638 static BuildSetting getBuildSettingBitField(string requestedData) 639 { 640 switch (requestedData) 641 { 642 case "dflags": return BuildSetting.dflags; 643 case "lflags": return BuildSetting.lflags; 644 case "libs": return BuildSetting.libs; 645 case "sourceFiles": return BuildSetting.sourceFiles; 646 case "copyFiles": return BuildSetting.copyFiles; 647 case "versions": return BuildSetting.versions; 648 case "debugVersions": return BuildSetting.debugVersions; 649 case "importPaths": return BuildSetting.importPaths; 650 case "stringImportPaths": return BuildSetting.stringImportPaths; 651 case "options": return BuildSetting.options; 652 default: assert(0); 653 } 654 } 655 656 auto targetDescription = projectDescription.targetLookup[projectDescription.rootPackage]; 657 auto buildSettings = targetDescription.buildSettings; 658 659 string[] values; 660 switch (attributeName) 661 { 662 case "dflags": 663 case "mainSourceFile": 664 case "libFiles": 665 case "importFiles": 666 values = formatBuildSettingPlain!attributeName(platform, configs, projectDescription); 667 break; 668 669 case "lflags": 670 case "sourceFiles": 671 case "versions": 672 case "debugVersions": 673 case "importPaths": 674 case "stringImportPaths": 675 case "options": 676 auto bs = buildSettings.dup; 677 bs.dflags = null; 678 679 // Ensure trailing slash on directory paths 680 auto ensureTrailingSlash = (string path) => path.endsWith(dirSeparator) ? path : path ~ dirSeparator; 681 static if (attributeName == "importPaths") 682 bs.importPaths = bs.importPaths.map!(ensureTrailingSlash).array(); 683 else static if (attributeName == "stringImportPaths") 684 bs.stringImportPaths = bs.stringImportPaths.map!(ensureTrailingSlash).array(); 685 686 compiler.prepareBuildSettings(bs, BuildSetting.all & ~getBuildSettingBitField(attributeName)); 687 values = bs.dflags; 688 break; 689 690 case "libs": 691 auto bs = buildSettings.dup; 692 bs.dflags = null; 693 bs.lflags = null; 694 bs.sourceFiles = null; 695 bs.targetType = TargetType.none; // Force Compiler to NOT omit dependency libs when package is a library. 696 697 compiler.prepareBuildSettings(bs, BuildSetting.all & ~getBuildSettingBitField(attributeName)); 698 699 if (bs.lflags) 700 values = bs.lflags; 701 else if (bs.sourceFiles) 702 values = bs.sourceFiles; 703 else 704 values = bs.dflags; 705 706 break; 707 708 default: assert(0); 709 } 710 711 // Escape filenames and paths 712 if(!disableEscaping) 713 { 714 switch (attributeName) 715 { 716 case "mainSourceFile": 717 case "libFiles": 718 case "copyFiles": 719 case "importFiles": 720 case "stringImportFiles": 721 case "sourceFiles": 722 case "importPaths": 723 case "stringImportPaths": 724 return values.map!(escapeShellFileName).array(); 725 726 default: 727 return values; 728 } 729 } 730 731 return values; 732 } 733 734 // Output a build setting without formatting for any particular compiler 735 private string[] formatBuildSettingPlain(string attributeName)(BuildPlatform platform, string[string] configs, ProjectDescription projectDescription) 736 { 737 import std.path : buildPath, dirSeparator; 738 import std.range : only; 739 740 string[] list; 741 742 auto targetDescription = projectDescription.targetLookup[projectDescription.rootPackage]; 743 auto buildSettings = targetDescription.buildSettings; 744 745 // Return any BuildSetting member attributeName as a range of strings. Don't attempt to fixup values. 746 // allowEmptyString: When the value is a string (as opposed to string[]), 747 // is empty string an actual permitted value instead of 748 // a missing value? 749 auto getRawBuildSetting(Package pack, bool allowEmptyString) { 750 auto value = __traits(getMember, buildSettings, attributeName); 751 752 static if( is(typeof(value) == string[]) ) 753 return value; 754 else static if( is(typeof(value) == string) ) 755 { 756 auto ret = only(value); 757 758 // only() has a different return type from only(value), so we 759 // have to empty the range rather than just returning only(). 760 if(value.empty && !allowEmptyString) { 761 ret.popFront(); 762 assert(ret.empty); 763 } 764 765 return ret; 766 } 767 else static if( is(typeof(value) == enum) ) 768 return only(value); 769 else static if( is(typeof(value) == BuildRequirements) ) 770 return only(cast(BuildRequirement) cast(int) value.values); 771 else static if( is(typeof(value) == BuildOptions) ) 772 return only(cast(BuildOption) cast(int) value.values); 773 else 774 static assert(false, "Type of BuildSettings."~attributeName~" is unsupported."); 775 } 776 777 // Adjust BuildSetting member attributeName as needed. 778 // Returns a range of strings. 779 auto getFixedBuildSetting(Package pack) { 780 // Is relative path(s) to a directory? 781 enum isRelativeDirectory = 782 attributeName == "importPaths" || attributeName == "stringImportPaths" || 783 attributeName == "targetPath" || attributeName == "workingDirectory"; 784 785 // Is relative path(s) to a file? 786 enum isRelativeFile = 787 attributeName == "sourceFiles" || attributeName == "importFiles" || 788 attributeName == "stringImportFiles" || attributeName == "copyFiles" || 789 attributeName == "mainSourceFile"; 790 791 // For these, empty string means "main project directory", not "missing value" 792 enum allowEmptyString = 793 attributeName == "targetPath" || attributeName == "workingDirectory"; 794 795 enum isEnumBitfield = 796 attributeName == "targetType" || attributeName == "requirements" || 797 attributeName == "options"; 798 799 auto values = getRawBuildSetting(pack, allowEmptyString); 800 auto fixRelativePath = (string importPath) => buildPath(pack.path.toString(), importPath); 801 auto ensureTrailingSlash = (string path) => path.endsWith(dirSeparator) ? path : path ~ dirSeparator; 802 803 static if(isRelativeDirectory) { 804 // Return full paths for the paths, making sure a 805 // directory separator is on the end of each path. 806 return values.map!(fixRelativePath).map!(ensureTrailingSlash); 807 } 808 else static if(isRelativeFile) { 809 // Return full paths. 810 return values.map!(fixRelativePath); 811 } 812 else static if(isEnumBitfield) 813 return bitFieldNames(values.front); 814 else 815 return values; 816 } 817 818 foreach(value; getFixedBuildSetting(m_rootPackage)) { 819 list ~= value; 820 } 821 822 return list; 823 } 824 825 // The "compiler" arg is for choosing which compiler the output should be formatted for, 826 // or null to imply "list" format. 827 private string[] listBuildSetting(BuildPlatform platform, string[string] configs, 828 ProjectDescription projectDescription, string requestedData, Compiler compiler, bool disableEscaping) 829 { 830 // Certain data cannot be formatter for a compiler 831 if (compiler) 832 { 833 switch (requestedData) 834 { 835 case "target-type": 836 case "target-path": 837 case "target-name": 838 case "working-directory": 839 case "string-import-files": 840 case "copy-files": 841 case "pre-generate-commands": 842 case "post-generate-commands": 843 case "pre-build-commands": 844 case "post-build-commands": 845 enforce(false, "--data="~requestedData~" can only be used with --data-format=list."); 846 break; 847 848 case "requirements": 849 enforce(false, "--data=requirements can only be used with --data-format=list. Use --data=options instead."); 850 break; 851 852 default: break; 853 } 854 } 855 856 import std.typetuple : TypeTuple; 857 auto args = TypeTuple!(platform, configs, projectDescription, compiler, disableEscaping); 858 switch (requestedData) 859 { 860 case "target-type": return listBuildSetting!"targetType"(args); 861 case "target-path": return listBuildSetting!"targetPath"(args); 862 case "target-name": return listBuildSetting!"targetName"(args); 863 case "working-directory": return listBuildSetting!"workingDirectory"(args); 864 case "main-source-file": return listBuildSetting!"mainSourceFile"(args); 865 case "dflags": return listBuildSetting!"dflags"(args); 866 case "lflags": return listBuildSetting!"lflags"(args); 867 case "libs": return listBuildSetting!"libs"(args); 868 case "lib-files": return listBuildSetting!"libFiles"(args); 869 case "source-files": return listBuildSetting!"sourceFiles"(args); 870 case "copy-files": return listBuildSetting!"copyFiles"(args); 871 case "versions": return listBuildSetting!"versions"(args); 872 case "debug-versions": return listBuildSetting!"debugVersions"(args); 873 case "import-paths": return listBuildSetting!"importPaths"(args); 874 case "string-import-paths": return listBuildSetting!"stringImportPaths"(args); 875 case "import-files": return listBuildSetting!"importFiles"(args); 876 case "string-import-files": return listBuildSetting!"stringImportFiles"(args); 877 case "pre-generate-commands": return listBuildSetting!"preGenerateCommands"(args); 878 case "post-generate-commands": return listBuildSetting!"postGenerateCommands"(args); 879 case "pre-build-commands": return listBuildSetting!"preBuildCommands"(args); 880 case "post-build-commands": return listBuildSetting!"postBuildCommands"(args); 881 case "requirements": return listBuildSetting!"requirements"(args); 882 case "options": return listBuildSetting!"options"(args); 883 884 default: 885 enforce(false, "--data="~requestedData~ 886 " is not a valid option. See 'dub describe --help' for accepted --data= values."); 887 } 888 889 assert(0); 890 } 891 892 /// Outputs requested data for the project, optionally including its dependencies. 893 string[] listBuildSettings(BuildPlatform platform, string config, string buildType, 894 string[] requestedData, Compiler formattingCompiler, bool nullDelim) 895 { 896 auto projectDescription = describe(platform, config, buildType); 897 auto configs = getPackageConfigs(platform, config); 898 899 // Include link dependencies 900 auto target = projectDescription.targetLookup[projectDescription.rootPackage]; 901 auto bs = target.buildSettings; 902 foreach (ldep; target.linkDependencies) { 903 auto dbs = projectDescription.targetLookup[ldep].buildSettings; 904 if (bs.targetType != TargetType.staticLibrary) { 905 bs.addLibFiles((Path(dbs.targetPath) ~ getTargetFileName(dbs, platform)).toNativeString()); 906 } 907 } 908 target.buildSettings = bs; 909 910 // Update projectDescription.targets 911 projectDescription.targetLookup[projectDescription.rootPackage] = target; 912 foreach (ref t; projectDescription.targets) { 913 if(t.rootPackage == target.rootPackage) { 914 t = target; 915 break; 916 } 917 } 918 919 // Genrate results 920 if (formattingCompiler) 921 { 922 // Format for a compiler 923 return [ 924 requestedData 925 .map!(dataName => listBuildSetting(platform, configs, projectDescription, dataName, formattingCompiler, nullDelim)) 926 .join().join(nullDelim? "\0" : " ") 927 ]; 928 } 929 else 930 { 931 // Format list-style 932 return requestedData 933 .map!(dataName => listBuildSetting(platform, configs, projectDescription, dataName, null, nullDelim)) 934 .joiner([""]) // Blank entry between each type of requestedData 935 .array(); 936 } 937 } 938 939 /// Outputs the import paths for the project, including its dependencies. 940 string[] listImportPaths(BuildPlatform platform, string config, string buildType, bool nullDelim) 941 { 942 auto projectDescription = describe(platform, config, buildType); 943 return listBuildSetting!"importPaths"(platform, config, projectDescription, null, nullDelim); 944 } 945 946 /// Outputs the string import paths for the project, including its dependencies. 947 string[] listStringImportPaths(BuildPlatform platform, string config, string buildType, bool nullDelim) 948 { 949 auto projectDescription = describe(platform, config, buildType); 950 return listBuildSetting!"stringImportPaths"(platform, config, projectDescription, null, nullDelim); 951 } 952 953 void saveSelections() 954 { 955 assert(m_selections !is null, "Cannot save selections for non-disk based project (has no selections)."); 956 if (m_selections.hasSelectedVersion(m_rootPackage.basePackage.name)) 957 m_selections.deselectVersion(m_rootPackage.basePackage.name); 958 959 auto path = m_rootPackage.path ~ SelectedVersions.defaultFile; 960 if (m_selections.dirty || !existsFile(path)) 961 m_selections.save(path); 962 } 963 964 bool isUpgradeCacheUpToDate() 965 { 966 try { 967 auto datestr = m_packageSettings["dub"].opt!(Json[string]).get("lastUpgrade", Json("")).get!string; 968 if (!datestr.length) return false; 969 auto date = SysTime.fromISOExtString(datestr); 970 if ((Clock.currTime() - date) > 1.days) return false; 971 return true; 972 } catch (Exception t) { 973 logDebug("Failed to get the last upgrade time: %s", t.msg); 974 return false; 975 } 976 } 977 978 Dependency[string] getUpgradeCache() 979 { 980 try { 981 Dependency[string] ret; 982 foreach (string p, d; m_packageSettings["dub"].opt!(Json[string]).get("cachedUpgrades", Json.emptyObject)) 983 ret[p] = SelectedVersions.dependencyFromJson(d); 984 return ret; 985 } catch (Exception t) { 986 logDebug("Failed to get cached upgrades: %s", t.msg); 987 return null; 988 } 989 } 990 991 void setUpgradeCache(Dependency[string] versions) 992 { 993 logDebug("markUpToDate"); 994 Json create(ref Json json, string object) { 995 if (json[object].type == Json.Type.undefined) json[object] = Json.emptyObject; 996 return json[object]; 997 } 998 create(m_packageSettings, "dub"); 999 m_packageSettings["dub"]["lastUpgrade"] = Clock.currTime().toISOExtString(); 1000 1001 create(m_packageSettings["dub"], "cachedUpgrades"); 1002 foreach (p, d; versions) 1003 m_packageSettings["dub"]["cachedUpgrades"][p] = SelectedVersions.dependencyToJson(d); 1004 1005 writeDubJson(); 1006 } 1007 1008 private void writeDubJson() { 1009 // don't bother to write an empty file 1010 if( m_packageSettings.length == 0 ) return; 1011 1012 try { 1013 logDebug("writeDubJson"); 1014 auto dubpath = m_rootPackage.path~".dub"; 1015 if( !exists(dubpath.toNativeString()) ) mkdir(dubpath.toNativeString()); 1016 auto dstFile = openFile((dubpath~"dub.json").toString(), FileMode.CreateTrunc); 1017 scope(exit) dstFile.close(); 1018 dstFile.writePrettyJsonString(m_packageSettings); 1019 } catch( Exception e ){ 1020 logWarn("Could not write .dub/dub.json."); 1021 } 1022 } 1023 } 1024 1025 /// Actions to be performed by the dub 1026 struct Action { 1027 enum Type { 1028 fetch, 1029 remove, 1030 conflict, 1031 failure 1032 } 1033 1034 immutable { 1035 Type type; 1036 string packageId; 1037 PlacementLocation location; 1038 Dependency vers; 1039 Version existingVersion; 1040 } 1041 const Package pack; 1042 const Dependency[string] issuer; 1043 1044 static Action get(string pkg, PlacementLocation location, in Dependency dep, Dependency[string] context, Version old_version = Version.UNKNOWN) 1045 { 1046 return Action(Type.fetch, pkg, location, dep, context, old_version); 1047 } 1048 1049 static Action remove(Package pkg, Dependency[string] context) 1050 { 1051 return Action(Type.remove, pkg, context); 1052 } 1053 1054 static Action conflict(string pkg, in Dependency dep, Dependency[string] context) 1055 { 1056 return Action(Type.conflict, pkg, PlacementLocation.user, dep, context); 1057 } 1058 1059 static Action failure(string pkg, in Dependency dep, Dependency[string] context) 1060 { 1061 return Action(Type.failure, pkg, PlacementLocation.user, dep, context); 1062 } 1063 1064 private this(Type id, string pkg, PlacementLocation location, in Dependency d, Dependency[string] issue, Version existing_version = Version.UNKNOWN) 1065 { 1066 this.type = id; 1067 this.packageId = pkg; 1068 this.location = location; 1069 this.vers = d; 1070 this.issuer = issue; 1071 this.existingVersion = existing_version; 1072 } 1073 1074 private this(Type id, Package pkg, Dependency[string] issue) 1075 { 1076 pack = pkg; 1077 type = id; 1078 packageId = pkg.name; 1079 vers = cast(immutable)Dependency(pkg.ver); 1080 issuer = issue; 1081 } 1082 1083 string toString() const { 1084 return to!string(type) ~ ": " ~ packageId ~ ", " ~ to!string(vers); 1085 } 1086 } 1087 1088 1089 /// Indicates where a package has been or should be placed to. 1090 enum PlacementLocation { 1091 /// Packages retrived with 'local' will be placed in the current folder 1092 /// using the package name as destination. 1093 local, 1094 /// Packages with 'userWide' will be placed in a folder accessible by 1095 /// all of the applications from the current user. 1096 user, 1097 /// Packages retrieved with 'systemWide' will be placed in a shared folder, 1098 /// which can be accessed by all users of the system. 1099 system 1100 } 1101 1102 /// The default placement location of fetched packages. Can be changed by --local or --system. 1103 auto defaultPlacementLocation = PlacementLocation.user; 1104 1105 void processVars(ref BuildSettings dst, in Project project, in Package pack, BuildSettings settings, bool include_target_settings = false) 1106 1107 { 1108 dst.addDFlags(processVars(project, pack, settings.dflags)); 1109 dst.addLFlags(processVars(project, pack, settings.lflags)); 1110 dst.addLibs(processVars(project, pack, settings.libs)); 1111 dst.addSourceFiles(processVars(project, pack, settings.sourceFiles, true)); 1112 dst.addImportFiles(processVars(project, pack, settings.importFiles, true)); 1113 dst.addStringImportFiles(processVars(project, pack, settings.stringImportFiles, true)); 1114 dst.addCopyFiles(processVars(project, pack, settings.copyFiles, true)); 1115 dst.addVersions(processVars(project, pack, settings.versions)); 1116 dst.addDebugVersions(processVars(project, pack, settings.debugVersions)); 1117 dst.addImportPaths(processVars(project, pack, settings.importPaths, true)); 1118 dst.addStringImportPaths(processVars(project, pack, settings.stringImportPaths, true)); 1119 dst.addPreGenerateCommands(processVars(project, pack, settings.preGenerateCommands)); 1120 dst.addPostGenerateCommands(processVars(project, pack, settings.postGenerateCommands)); 1121 dst.addPreBuildCommands(processVars(project, pack, settings.preBuildCommands)); 1122 dst.addPostBuildCommands(processVars(project, pack, settings.postBuildCommands)); 1123 dst.addRequirements(settings.requirements); 1124 dst.addOptions(settings.options); 1125 1126 if (include_target_settings) { 1127 dst.targetType = settings.targetType; 1128 dst.targetPath = processVars(settings.targetPath, project, pack, true); 1129 dst.targetName = settings.targetName; 1130 if (!settings.workingDirectory.empty) 1131 dst.workingDirectory = processVars(settings.workingDirectory, project, pack, true); 1132 if (settings.mainSourceFile.length) 1133 dst.mainSourceFile = processVars(settings.mainSourceFile, project, pack, true); 1134 } 1135 } 1136 1137 private string[] processVars(in Project project, in Package pack, string[] vars, bool are_paths = false) 1138 { 1139 auto ret = appender!(string[])(); 1140 processVars(ret, project, pack, vars, are_paths); 1141 return ret.data; 1142 1143 } 1144 private void processVars(ref Appender!(string[]) dst, in Project project, in Package pack, string[] vars, bool are_paths = false) 1145 { 1146 foreach (var; vars) dst.put(processVars(var, project, pack, are_paths)); 1147 } 1148 1149 private string processVars(string var, in Project project, in Package pack, bool is_path) 1150 { 1151 auto idx = std..string.indexOf(var, '$'); 1152 if (idx >= 0) { 1153 auto vres = appender!string(); 1154 while (idx >= 0) { 1155 if (idx+1 >= var.length) break; 1156 if (var[idx+1] == '$') { 1157 vres.put(var[0 .. idx+1]); 1158 var = var[idx+2 .. $]; 1159 } else { 1160 vres.put(var[0 .. idx]); 1161 var = var[idx+1 .. $]; 1162 1163 size_t idx2 = 0; 1164 while( idx2 < var.length && isIdentChar(var[idx2]) ) idx2++; 1165 auto varname = var[0 .. idx2]; 1166 var = var[idx2 .. $]; 1167 1168 vres.put(getVariable(varname, project, pack)); 1169 } 1170 idx = std..string.indexOf(var, '$'); 1171 } 1172 vres.put(var); 1173 var = vres.data; 1174 } 1175 if (is_path) { 1176 auto p = Path(var); 1177 if (!p.absolute) { 1178 logDebug("Fixing relative path: %s ~ %s", pack.path.toNativeString(), p.toNativeString()); 1179 return (pack.path ~ p).toNativeString(); 1180 } else return p.toNativeString(); 1181 } else return var; 1182 } 1183 1184 private string getVariable(string name, in Project project, in Package pack) 1185 { 1186 if (name == "PACKAGE_DIR") return pack.path.toNativeString(); 1187 if (name == "ROOT_PACKAGE_DIR") return project.rootPackage.path.toNativeString(); 1188 1189 if (name.endsWith("_PACKAGE_DIR")) { 1190 auto pname = name[0 .. $-12]; 1191 foreach (prj; project.getTopologicalPackageList()) 1192 if (prj.name.toUpper().replace("-", "_") == pname) 1193 return prj.path.toNativeString(); 1194 } 1195 1196 auto envvar = environment.get(name); 1197 if (envvar !is null) return envvar; 1198 1199 throw new Exception("Invalid variable: "~name); 1200 } 1201 1202 private bool isIdentChar(dchar ch) 1203 { 1204 return ch >= 'A' && ch <= 'Z' || ch >= 'a' && ch <= 'z' || ch >= '0' && ch <= '9' || ch == '_'; 1205 } 1206 1207 string stripDlangSpecialChars(string s) 1208 { 1209 import std.array; 1210 import std.uni; 1211 auto ret = appender!string(); 1212 foreach(ch; s) 1213 ret.put(isIdentChar(ch) ? ch : '_'); 1214 return ret.data; 1215 } 1216 1217 final class SelectedVersions { 1218 private struct Selected { 1219 Dependency dep; 1220 //Dependency[string] packages; 1221 } 1222 private { 1223 enum FileVersion = 1; 1224 Selected[string] m_selections; 1225 bool m_dirty = false; // has changes since last save 1226 } 1227 1228 enum defaultFile = "dub.selections.json"; 1229 1230 this() {} 1231 1232 this(Json data) 1233 { 1234 deserialize(data); 1235 m_dirty = false; 1236 } 1237 1238 this(Path path) 1239 { 1240 auto json = jsonFromFile(path); 1241 deserialize(json); 1242 m_dirty = false; 1243 } 1244 1245 @property string[] selectedPackages() const { return m_selections.keys; } 1246 1247 @property bool dirty() const { return m_dirty; } 1248 1249 void clear() 1250 { 1251 m_selections = null; 1252 m_dirty = true; 1253 } 1254 1255 void set(SelectedVersions versions) 1256 { 1257 m_selections = versions.m_selections.dup; 1258 m_dirty = true; 1259 } 1260 1261 void selectVersion(string package_id, Version version_) 1262 { 1263 if (auto ps = package_id in m_selections) { 1264 if (ps.dep == Dependency(version_)) 1265 return; 1266 } 1267 m_selections[package_id] = Selected(Dependency(version_)/*, issuer*/); 1268 m_dirty = true; 1269 } 1270 1271 void selectVersion(string package_id, Path path) 1272 { 1273 if (auto ps = package_id in m_selections) { 1274 if (ps.dep == Dependency(path)) 1275 return; 1276 } 1277 m_selections[package_id] = Selected(Dependency(path)); 1278 m_dirty = true; 1279 } 1280 1281 void deselectVersion(string package_id) 1282 { 1283 m_selections.remove(package_id); 1284 m_dirty = true; 1285 } 1286 1287 bool hasSelectedVersion(string packageId) 1288 const { 1289 return (packageId in m_selections) !is null; 1290 } 1291 1292 Dependency getSelectedVersion(string packageId) 1293 const { 1294 enforce(hasSelectedVersion(packageId)); 1295 return m_selections[packageId].dep; 1296 } 1297 1298 void save(Path path) 1299 { 1300 Json json = serialize(); 1301 auto file = openFile(path, FileMode.CreateTrunc); 1302 scope(exit) file.close(); 1303 file.writePrettyJsonString(json); 1304 file.put('\n'); 1305 m_dirty = false; 1306 } 1307 1308 static Json dependencyToJson(Dependency d) 1309 { 1310 if (d.path.empty) return Json(d.version_.toString()); 1311 else return serializeToJson(["path": d.path.toString()]); 1312 } 1313 1314 static Dependency dependencyFromJson(Json j) 1315 { 1316 if (j.type == Json.Type..string) 1317 return Dependency(Version(j.get!string)); 1318 else if (j.type == Json.Type.object) 1319 return Dependency(Path(j.path.get!string)); 1320 else throw new Exception(format("Unexpected type for dependency: %s", j.type)); 1321 } 1322 1323 Json serialize() 1324 const { 1325 Json json = serializeToJson(m_selections); 1326 Json serialized = Json.emptyObject; 1327 serialized.fileVersion = FileVersion; 1328 serialized.versions = Json.emptyObject; 1329 foreach (p, v; m_selections) 1330 serialized.versions[p] = dependencyToJson(v.dep); 1331 return serialized; 1332 } 1333 1334 private void deserialize(Json json) 1335 { 1336 enforce(cast(int)json["fileVersion"] == FileVersion, "Mismatched dub.select.json version: " ~ to!string(cast(int)json["fileVersion"]) ~ "vs. " ~to!string(FileVersion)); 1337 clear(); 1338 scope(failure) clear(); 1339 foreach (string p, v; json.versions) 1340 m_selections[p] = Selected(dependencyFromJson(v)); 1341 } 1342 } 1343