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.internal.utils; 13 import dub.internal.std.process; 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.string; 32 import std.typecons; 33 import std.zip; 34 35 36 /// Representing a full project, with a root Package and several dependencies. 37 class Project { 38 private { 39 bool m_fixedPackage; 40 Path m_root; 41 PackageManager m_packageManager; 42 Json m_json; 43 Package m_main; 44 //Package[string] m_packages; 45 Package[] m_dependencies; 46 Package[][Package] m_dependees; 47 } 48 49 this(PackageManager package_manager, Path project_path) 50 { 51 m_packageManager = package_manager; 52 m_root = project_path; 53 m_fixedPackage = false; 54 m_json = Json.EmptyObject; 55 reinit(); 56 } 57 58 this(PackageManager package_manager, Package pack) 59 { 60 m_packageManager = package_manager; 61 m_root = pack.path; 62 m_main = pack; 63 m_fixedPackage = true; 64 m_json = Json.EmptyObject; 65 reinit(); 66 } 67 68 /// Gathers information 69 @property string info() 70 const { 71 if(!m_main) 72 return "-Unrecognized application in '"~m_root.toNativeString()~"' (probably no package.json in this directory)"; 73 string s = "-Application identifier: " ~ m_main.name; 74 s ~= "\n" ~ m_main.generateInfoString(); 75 s ~= "\n-Installed dependencies:"; 76 foreach(p; m_dependencies) 77 s ~= "\n" ~ p.generateInfoString(); 78 return s; 79 } 80 81 /// Gets all installed packages as a "packageId" = "version" associative array 82 @property string[string] installedPackagesIDs() const { 83 string[string] pkgs; 84 foreach(p; m_dependencies) 85 pkgs[p.name] = p.vers; 86 return pkgs; 87 } 88 89 /// List of installed Packages 90 @property const(Package[]) dependencies() const { return m_dependencies; } 91 92 /// Main package. 93 @property const (Package) mainPackage() const { return m_main; } 94 95 /** Allows iteration of the dependency tree in topological order 96 */ 97 int delegate(int delegate(ref const Package)) getTopologicalPackageList(bool children_first = false, in Package root_package = null, string[string] configs = null) 98 const { 99 const(Package) rootpack = root_package ? root_package : m_main; 100 101 int iterator(int delegate(ref const Package) del) 102 { 103 int ret = 0; 104 bool[const(Package)] visited; 105 void perform_rec(in Package p){ 106 if( p in visited ) return; 107 visited[p] = true; 108 109 if( !children_first ){ 110 ret = del(p); 111 if( ret ) return; 112 } 113 114 auto cfg = configs.get(p.name, null); 115 116 foreach (dn, dv; p.dependencies) { 117 // filter out dependencies not in the current configuration set 118 if (!p.hasDependency(dn, cfg)) continue; 119 auto dependency = getDependency(dn, dv.optional); 120 if(dependency) perform_rec(dependency); 121 if( ret ) return; 122 } 123 124 if( children_first ){ 125 ret = del(p); 126 if( ret ) return; 127 } 128 } 129 perform_rec(rootpack); 130 return ret; 131 } 132 133 return &iterator; 134 } 135 136 inout(Package) getDependency(string name, bool isOptional) 137 inout { 138 foreach(dp; m_dependencies) 139 if( dp.name == name ) 140 return dp; 141 if(!isOptional) throw new Exception("Unknown dependency: "~name); 142 else return null; 143 } 144 145 string getDefaultConfiguration(BuildPlatform platform) 146 const { 147 return m_main.getDefaultConfiguration(platform, true); 148 } 149 150 /// Rereads the applications state. 151 void reinit() 152 { 153 scope(failure){ 154 logDiagnostic("Failed to initialize project. Assuming defaults."); 155 if (!m_fixedPackage) m_main = new Package(serializeToJson(["name": "unknown"]), m_root); 156 } 157 158 m_dependencies = null; 159 m_packageManager.refresh(false); 160 161 try m_json = jsonFromFile(m_root ~ ".dub/dub.json", true); 162 catch(Exception t) logDiagnostic("Failed to read .dub/dub.json: %s", t.msg); 163 164 // load package description 165 if (!m_fixedPackage) { 166 if (!existsFile(m_root~PackageJsonFilename)) { 167 logWarn("There was no '"~PackageJsonFilename~"' found for the application in '%s'.", m_root.toNativeString()); 168 auto json = Json.EmptyObject; 169 json.name = "unknown"; 170 m_main = new Package(json, m_root); 171 return; 172 } 173 174 m_main = m_packageManager.getPackage(m_root); 175 } 176 177 // some basic package lint 178 m_main.warnOnSpecialCompilerFlags(); 179 if (m_main.name != m_main.name.toLower()) { 180 logWarn(`DUB package names should always be lower case, please change to {"name": "%s"}. You can use {"targetName": "%s"} to keep the current executable name.`, 181 m_main.name.toLower(), m_main.name); 182 } 183 184 // TODO: compute the set of mutual dependencies first 185 // (i.e. ">=0.0.1 <=0.0.5" and "<= 0.0.4" get ">=0.0.1 <=0.0.4") 186 // conflicts would then also be detected. 187 void collectDependenciesRec(Package pack) 188 { 189 logDiagnostic("Collecting dependencies for %s", pack.name); 190 foreach( name, vspec; pack.dependencies ){ 191 Package p; 192 if( !vspec.path.empty ){ 193 Path path = vspec.path; 194 if( !path.absolute ) path = pack.path ~ path; 195 logDiagnostic("Adding local %s %s", path, vspec.version_); 196 p = m_packageManager.getTemporaryPackage(path, vspec.version_); 197 } else { 198 p = m_packageManager.getBestPackage(name, vspec); 199 } 200 if( !m_dependencies.canFind(p) ){ 201 logDiagnostic("Found dependency %s %s: %s", name, vspec.toString(), p !is null); 202 if( p ){ 203 m_dependencies ~= p; 204 p.warnOnSpecialCompilerFlags(); 205 collectDependenciesRec(p); 206 } 207 } 208 m_dependees[p] ~= pack; 209 //enforce(p !is null, "Failed to resolve dependency "~name~" "~vspec.toString()); 210 } 211 } 212 collectDependenciesRec(m_main); 213 } 214 215 /// Returns the applications name. 216 @property string name() const { return m_main ? m_main.name : "app"; } 217 218 @property string[] configurations() const { return m_main.configurations; } 219 220 /// Returns a map with the configuration for all packages in the dependency tree. 221 string[string] getPackageConfigs(in BuildPlatform platform, string config) 222 const { 223 struct Vertex { string pack, config; } 224 struct Edge { size_t from, to; } 225 226 Vertex[] configs; 227 Edge[] edges; 228 string[][string] parents; 229 parents[m_main.name] = null; 230 foreach (p; getTopologicalPackageList()) 231 foreach (d; p.dependencies.byKey) 232 parents[d] ~= p.name; 233 234 235 size_t createConfig(string pack, string config) { 236 foreach (i, v; configs) 237 if (v.pack == pack && v.config == config) 238 return i; 239 configs ~= Vertex(pack, config); 240 return configs.length-1; 241 } 242 243 size_t createEdge(size_t from, size_t to) { 244 auto idx = edges.countUntil(Edge(from, to)); 245 if (idx >= 0) return idx; 246 edges ~= Edge(from, to); 247 return edges.length-1; 248 } 249 250 void removeConfig(size_t i) { 251 logDebug("Eliminating config %s for %s", configs[i].config, configs[i].pack); 252 configs = configs.remove(i); 253 edges = edges.filter!(e => e.from != i && e.to != i).array(); 254 foreach (ref e; edges) { 255 if (e.from > i) e.from--; 256 if (e.to > i) e.to--; 257 } 258 } 259 260 bool isReachable(string pack, string conf) { 261 if (pack == configs[0].pack && configs[0].config == conf) return true; 262 foreach (e; edges) 263 if (configs[e.to].pack == pack && configs[e.to].config == conf) 264 return true; 265 return false; 266 //return (pack == configs[0].pack && conf == configs[0].config) || edges.canFind!(e => configs[e.to].pack == pack && configs[e.to].config == config); 267 } 268 269 bool isReachableByAllParentPacks(size_t cidx) { 270 bool[string] r; 271 foreach (p; parents[configs[cidx].pack]) r[p] = false; 272 foreach (e; edges) { 273 if (e.to != cidx) continue; 274 if (auto pp = configs[e.from].pack in r) *pp = true; 275 } 276 foreach (bool v; r) if (!v) return false; 277 return true; 278 } 279 280 // create a graph of all possible package configurations (package, config) -> (subpackage, subconfig) 281 void determineAllConfigs(in Package p) 282 { 283 foreach (c; p.getPlatformConfigurations(platform, p is m_main)) { 284 if (!isReachable(p.name, c)) { 285 //foreach (e; edges) logDebug(" %s %s -> %s %s", configs[e.from].pack, configs[e.from].config, configs[e.to].pack, configs[e.to].config); 286 logDebug("Skipping %s %s", p.name, c); 287 continue; 288 } 289 size_t cidx = createConfig(p.name, c); 290 foreach (dn; p.dependencies.byKey) { 291 auto dp = getDependency(dn, true); 292 if (!dp) continue; 293 auto subconf = p.getSubConfiguration(c, dp, platform); 294 if (subconf.empty) { 295 foreach (sc; dp.getPlatformConfigurations(platform)) { 296 logDebug("Including %s %s -> %s %s", p.name, c, dn, sc); 297 createEdge(cidx, createConfig(dn, sc)); 298 } 299 } else { 300 logDebug("Including %s %s -> %s %s", p.name, c, dn, subconf); 301 createEdge(cidx, createConfig(dn, subconf)); 302 } 303 } 304 foreach (dn; p.dependencies.byKey) { 305 auto dp = getDependency(dn, true); 306 if (!dp) continue; 307 determineAllConfigs(dp); 308 } 309 } 310 } 311 createConfig(m_main.name, config); 312 determineAllConfigs(m_main); 313 314 // successively remove configurations until only one configuration per package is left 315 bool changed; 316 do { 317 // remove all configs that are not reachable by all parent packages 318 changed = false; 319 for (size_t i = 0; i < configs.length; ) { 320 if (!isReachableByAllParentPacks(i)) { 321 removeConfig(i); 322 changed = true; 323 } else i++; 324 } 325 326 // when all edges are cleaned up, pick one package and remove all but one config 327 if (!changed) { 328 foreach (p; getTopologicalPackageList()) { 329 size_t cnt = 0; 330 for (size_t i = 0; i < configs.length; ) { 331 if (configs[i].pack == p.name) { 332 if (++cnt > 1) removeConfig(i); 333 else i++; 334 } else i++; 335 } 336 if (cnt > 1) { 337 changed = true; 338 break; 339 } 340 } 341 } 342 } while (changed); 343 344 // print out the resulting tree 345 foreach (e; edges) logDebug(" %s %s -> %s %s", configs[e.from].pack, configs[e.from].config, configs[e.to].pack, configs[e.to].config); 346 347 // return the resulting configuration set as an AA 348 string[string] ret; 349 foreach (c; configs) { 350 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])); 351 logDebug("Using configuration '%s' for %s", c.config, c.pack); 352 ret[c.pack] = c.config; 353 } 354 355 // check for conflicts (packages missing in the final configuration graph) 356 foreach (p; getTopologicalPackageList()) 357 enforce(p.name in ret, "Conflicting configurations for package "~p.name); 358 359 return ret; 360 } 361 362 /** 363 * Fills dst with values from this project. 364 * 365 * dst gets initialized according to the given platform and config. 366 * 367 * Params: 368 * dst = The BuildSettings struct to fill with data. 369 * platform = The platform to retrieve the values for. 370 * config = Values of the given configuration will be retrieved. 371 * root_package = If non null, use it instead of the project's real root package. 372 */ 373 void addBuildSettings(ref BuildSettings dst, in BuildPlatform platform, string config, in Package root_package = null) 374 const { 375 auto configs = getPackageConfigs(platform, config); 376 377 foreach (pkg; this.getTopologicalPackageList(false, root_package, configs)) { 378 dst.addVersions(["Have_" ~ stripDlangSpecialChars(pkg.name)]); 379 380 assert(pkg.name in configs, "Missing configuration for "~pkg.name); 381 logDebug("Gathering build settings for %s (%s)", pkg.name, configs[pkg.name]); 382 383 auto psettings = pkg.getBuildSettings(platform, configs[pkg.name]); 384 if (psettings.targetType != TargetType.none) { 385 processVars(dst, pkg.path.toNativeString(), psettings); 386 if (psettings.importPaths.empty) 387 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]); 388 } 389 if (pkg is m_main) { 390 enforce(psettings.targetType != TargetType.none, "Main package has target type \"none\" - stopping build."); 391 dst.targetType = psettings.targetType; 392 dst.targetPath = psettings.targetPath; 393 dst.targetName = psettings.targetName; 394 dst.workingDirectory = psettings.workingDirectory; 395 } 396 } 397 398 // always add all version identifiers of all packages 399 foreach (pkg; this.getTopologicalPackageList(false, null, configs)) { 400 auto psettings = pkg.getBuildSettings(platform, configs[pkg.name]); 401 dst.addVersions(psettings.versions); 402 } 403 } 404 405 void addBuildTypeSettings(ref BuildSettings dst, in BuildPlatform platform, string build_type) 406 { 407 bool usedefflags = !(dst.requirements & BuildRequirements.noDefaultFlags); 408 if (usedefflags) { 409 BuildSettings btsettings; 410 m_main.addBuildTypeSettings(btsettings, platform, build_type); 411 processVars(dst, m_main.path.toNativeString(), btsettings); 412 } 413 } 414 415 /// Determines if the given dependency is already indirectly referenced by other dependencies of pack. 416 bool isRedundantDependency(in Package pack, in Package dependency) 417 const { 418 foreach (dep; pack.dependencies.byKey) { 419 auto dp = getDependency(dep, true); 420 if (!dp) continue; 421 if (dp is dependency) continue; 422 foreach (ddp; getTopologicalPackageList(false, dp)) 423 if (ddp is dependency) return true; 424 } 425 return false; 426 } 427 428 429 /// Actions which can be performed to update the application. 430 Action[] determineActions(PackageSupplier[] packageSuppliers, int option) 431 { 432 scope(exit) writeDubJson(); 433 434 if(!m_main) { 435 Action[] a; 436 return a; 437 } 438 439 auto graph = new DependencyGraph(m_main); 440 if(!gatherMissingDependencies(packageSuppliers, graph) || graph.missing().length > 0) { 441 // Check the conflicts first. 442 auto conflicts = graph.conflicted(); 443 if(conflicts.length > 0) { 444 logError("The dependency graph could not be filled, there are conflicts."); 445 Action[] actions; 446 foreach( string pkg, dbp; graph.conflicted()) 447 actions ~= Action.conflict(pkg, dbp.dependency, dbp.packages); 448 449 // Missing dependencies could have some bogus results, therefore 450 // return only the conflicts. 451 return actions; 452 } 453 454 // Then check unresolved dependencies. 455 logError("The dependency graph could not be filled, there are unresolved dependencies."); 456 Action[] actions; 457 foreach( string pkg, rdp; graph.missing()) 458 actions ~= Action.failure(pkg, rdp.dependency, rdp.packages); 459 460 return actions; 461 } 462 463 // Gather installed 464 Package[string] installed; 465 installed[m_main.name] = m_main; 466 foreach(ref Package p; m_dependencies) { 467 auto pbase = p.basePackage; 468 auto pexist = installed.get(pbase.name, null); 469 if (pexist && pexist !is pbase){ 470 logError("The same package is referenced in different paths:"); 471 logError(" %s %s: %s", pexist.name, pexist.vers, pexist.path.toNativeString()); 472 logError(" %s %s: %s", pbase.name, pbase.vers, pbase.path.toNativeString()); 473 throw new Exception("Conflicting package multi-references."); 474 } 475 installed[pbase.name] = pbase; 476 } 477 478 // Check against installed and add install actions 479 Action[] actions; 480 int[string] upgradePackages; 481 foreach( string pkg, d; graph.needed() ) { 482 auto basepkg = pkg.getBasePackage(); 483 auto p = basepkg in installed; 484 // TODO: auto update to latest head revision 485 if(!p || (!d.dependency.matches(p.vers) && !d.dependency.matches(Version.MASTER))) { 486 if(!p) logDiagnostic("Triggering installation of required package '"~basepkg~"', which is not installed."); 487 else logDiagnostic("Triggering installation of required package '"~basepkg~"', which doesn't match the required versionh. Required '%s', available '%s'.", d.dependency, p.vers); 488 actions ~= Action.install(basepkg, InstallLocation.userWide, d.dependency, d.packages); 489 } else { 490 if( option & UpdateOptions.Upgrade ) { 491 // Only add one upgrade action for each package. 492 if(basepkg !in upgradePackages) { 493 logDiagnostic("Required package '"~basepkg~"' found with version '"~p.vers~"', upgrading."); 494 upgradePackages[basepkg] = 1; 495 actions ~= Action.install(basepkg, InstallLocation.userWide, d.dependency, d.packages); 496 } 497 } 498 else { 499 logDiagnostic("Required package '"~basepkg~"' found with version '"~p.vers~"'"); 500 } 501 } 502 } 503 504 return actions; 505 } 506 507 /// Outputs a JSON description of the project, including its deoendencies. 508 void describe(ref Json dst, BuildPlatform platform, string config) 509 { 510 dst.mainPackage = m_main.name; 511 512 auto configs = getPackageConfigs(platform, config); 513 514 auto mp = Json.EmptyObject; 515 m_main.describe(mp, platform, config); 516 dst.packages = Json([mp]); 517 518 foreach (dep; m_dependencies) { 519 auto dp = Json.EmptyObject; 520 dep.describe(dp, platform, configs[dep.name]); 521 dst.packages = dst.packages.get!(Json[]) ~ dp; 522 } 523 } 524 525 private bool gatherMissingDependencies(PackageSupplier[] packageSuppliers, DependencyGraph graph) 526 { 527 RequestedDependency[string] missing = graph.missing(); 528 RequestedDependency[string] oldMissing; 529 while( missing.length > 0 ) { 530 logDebug("Try to resolve %s", missing.keys); 531 if( missing.keys == oldMissing.keys ){ // FIXME: should actually compare the complete AA here 532 bool different = false; 533 foreach(string pkg, reqDep; missing) { 534 auto o = pkg in oldMissing; 535 if(o && reqDep.dependency != o.dependency) { 536 different = true; 537 break; 538 } 539 } 540 if(!different) { 541 logWarn("Could not resolve dependencies"); 542 return false; 543 } 544 } 545 546 oldMissing = missing.dup; 547 logDebug("There are %s packages missing.", missing.length); 548 549 auto toLookup = missing; 550 foreach(id, dep; graph.optional()) { 551 enforce(id !in toLookup, "A missing dependency in the graph seems to be optional, which is an error."); 552 toLookup[id] = dep; 553 } 554 555 foreach(string pkg, reqDep; toLookup) { 556 if(!reqDep.dependency.valid()) { 557 logDebug("Dependency to "~pkg~" is invalid. Trying to fix by modifying others."); 558 continue; 559 } 560 561 auto ppath = pkg.getSubPackagePath(); 562 563 // TODO: auto update and update interval by time 564 logDebug("Adding package to graph: "~pkg); 565 Package p = m_packageManager.getBestPackage(pkg, reqDep.dependency); 566 if( p ) logDebug("Found installed package %s %s", pkg, p.ver); 567 568 // Don't bother with not available optional packages. 569 if( !p && reqDep.dependency.optional ) continue; 570 571 // Try an already installed package first 572 if( p && needsUpToDateCheck(p) ){ 573 logInfo("Triggering update of package %s", pkg); 574 p = null; 575 } 576 577 if( !p ){ 578 try { 579 logDiagnostic("Fetching package %s (%d suppliers registered)", pkg, packageSuppliers.length); 580 foreach (ps; packageSuppliers) { 581 try { 582 auto sp = new Package(ps.getPackageDescription(ppath[0], reqDep.dependency)); 583 foreach (spn; ppath[1 .. $]) 584 sp = sp.getSubPackage(spn); 585 p = sp; 586 break; 587 } catch (Exception e) { 588 logDiagnostic("No metadata for %s: %s", ps.toString(), e.msg); 589 } 590 } 591 enforce(p !is null, "Could not find package candidate for "~pkg~" "~reqDep.dependency.toString()); 592 markUpToDate(ppath[0]); 593 } 594 catch(Throwable e) { 595 logError("Failed to retrieve metadata for package %s: %s", pkg, e.msg); 596 logDiagnostic("Full error: %s", e.toString().sanitize()); 597 } 598 } 599 600 if(p) 601 graph.insert(p); 602 } 603 graph.clearUnused(); 604 605 // As the dependencies are filled in starting from the outermost 606 // packages, resolving those conflicts won't happen (?). 607 if(graph.conflicted().length > 0) { 608 logInfo("There are conflicts in the dependency graph."); 609 return false; 610 } 611 612 missing = graph.missing(); 613 } 614 615 return true; 616 } 617 618 private bool needsUpToDateCheck(Package pack) { 619 version (none) { // needs to be updated for the new package system (where no project local packages exist) 620 try { 621 auto time = m_json["dub"]["lastUpdate"].opt!(Json[string]).get(pack.name, Json("")).get!string; 622 if( !time.length ) return true; 623 return (Clock.currTime() - SysTime.fromISOExtString(time)) > dur!"days"(1); 624 } catch(Exception t) return true; 625 } else return false; 626 } 627 628 private void markUpToDate(string packageId) { 629 logDebug("markUpToDate(%s)", packageId); 630 Json create(ref Json json, string object) { 631 if( object !in json ) json[object] = Json.EmptyObject; 632 return json[object]; 633 } 634 create(m_json, "dub"); 635 create(m_json["dub"], "lastUpdate"); 636 m_json["dub"]["lastUpdate"][packageId] = Json( Clock.currTime().toISOExtString() ); 637 638 writeDubJson(); 639 } 640 641 private void writeDubJson() { 642 // don't bother to write an empty file 643 if( m_json.length == 0 ) return; 644 645 try { 646 logDebug("writeDubJson"); 647 auto dubpath = m_root~".dub"; 648 if( !exists(dubpath.toNativeString()) ) mkdir(dubpath.toNativeString()); 649 auto dstFile = openFile((dubpath~"dub.json").toString(), FileMode.CreateTrunc); 650 scope(exit) dstFile.close(); 651 dstFile.writePrettyJsonString(m_json); 652 } catch( Exception e ){ 653 logWarn("Could not write .dub/dub.json."); 654 } 655 } 656 } 657 658 /// Actions to be performed by the dub 659 struct Action { 660 enum Type { 661 install, 662 uninstall, 663 conflict, 664 failure 665 } 666 667 immutable { 668 Type type; 669 string packageId; 670 InstallLocation location; 671 Dependency vers; 672 } 673 const Package pack; 674 const Dependency[string] issuer; 675 676 static Action install(string pkg, InstallLocation location, in Dependency dep, Dependency[string] context) 677 { 678 return Action(Type.install, pkg, location, dep, context); 679 } 680 681 static Action uninstall(Package pkg, Dependency[string] context) 682 { 683 return Action(Type.uninstall, pkg, context); 684 } 685 686 static Action conflict(string pkg, in Dependency dep, Dependency[string] context) 687 { 688 return Action(Type.conflict, pkg, InstallLocation.userWide, dep, context); 689 } 690 691 static Action failure(string pkg, in Dependency dep, Dependency[string] context) 692 { 693 return Action(Type.failure, pkg, InstallLocation.userWide, dep, context); 694 } 695 696 private this(Type id, string pkg, InstallLocation location, in Dependency d, Dependency[string] issue) 697 { 698 this.type = id; 699 this.packageId = pkg; 700 this.location = location; 701 this.vers = d; 702 this.issuer = issue; 703 } 704 705 private this(Type id, Package pkg, Dependency[string] issue) 706 { 707 pack = pkg; 708 type = id; 709 packageId = pkg.name; 710 vers = cast(immutable)Dependency(pkg.ver); 711 issuer = issue; 712 } 713 714 string toString() const { 715 return to!string(type) ~ ": " ~ packageId ~ ", " ~ to!string(vers); 716 } 717 } 718 719 enum UpdateOptions 720 { 721 None = 0, 722 JustAnnotate = 1<<0, 723 Upgrade = 1<<1 724 }; 725 726 727 private void processVars(ref BuildSettings dst, string project_path, BuildSettings settings) 728 { 729 dst.addDFlags(processVars(project_path, settings.dflags)); 730 dst.addLFlags(processVars(project_path, settings.lflags)); 731 dst.addLibs(processVars(project_path, settings.libs)); 732 dst.addSourceFiles(processVars(project_path, settings.sourceFiles, true)); 733 dst.addCopyFiles(processVars(project_path, settings.copyFiles, true)); 734 dst.addVersions(processVars(project_path, settings.versions)); 735 dst.addDebugVersions(processVars(project_path, settings.debugVersions)); 736 dst.addImportPaths(processVars(project_path, settings.importPaths, true)); 737 dst.addStringImportPaths(processVars(project_path, settings.stringImportPaths, true)); 738 dst.addPreGenerateCommands(processVars(project_path, settings.preGenerateCommands)); 739 dst.addPostGenerateCommands(processVars(project_path, settings.postGenerateCommands)); 740 dst.addPreBuildCommands(processVars(project_path, settings.preBuildCommands)); 741 dst.addPostBuildCommands(processVars(project_path, settings.postBuildCommands)); 742 dst.addRequirements(settings.requirements); 743 dst.addOptions(settings.options); 744 } 745 746 private string[] processVars(string project_path, string[] vars, bool are_paths = false) 747 { 748 auto ret = appender!(string[])(); 749 processVars(ret, project_path, vars, are_paths); 750 return ret.data; 751 752 } 753 private void processVars(ref Appender!(string[]) dst, string project_path, string[] vars, bool are_paths = false) 754 { 755 foreach( var; vars ){ 756 auto idx = std..string.indexOf(var, '$'); 757 if( idx >= 0 ){ 758 auto vres = appender!string(); 759 while( idx >= 0 ){ 760 if( idx+1 >= var.length ) break; 761 if( var[idx+1] == '$' ){ 762 vres.put(var[0 .. idx+1]); 763 var = var[idx+2 .. $]; 764 } else { 765 vres.put(var[0 .. idx]); 766 var = var[idx+1 .. $]; 767 768 size_t idx2 = 0; 769 while( idx2 < var.length && isIdentChar(var[idx2]) ) idx2++; 770 auto varname = var[0 .. idx2]; 771 var = var[idx2 .. $]; 772 773 if( varname == "PACKAGE_DIR" ) vres.put(project_path); 774 else enforce(false, "Invalid variable: "~varname); 775 } 776 idx = std..string.indexOf(var, '$'); 777 } 778 vres.put(var); 779 var = vres.data; 780 } 781 if( are_paths ){ 782 auto p = Path(var); 783 if( !p.absolute ){ 784 logDebug("Fixing relative path: %s ~ %s", project_path, p.toNativeString()); 785 p = Path(project_path) ~ p; 786 } 787 dst.put(p.toNativeString()); 788 } else dst.put(var); 789 } 790 } 791 792 private bool isIdentChar(dchar ch) 793 { 794 return ch >= 'A' && ch <= 'Z' || ch >= 'a' && ch <= 'z' || ch >= '0' && ch <= '9' || ch == '_'; 795 } 796 797 private string stripDlangSpecialChars(string s) 798 { 799 import std.array; 800 import std.uni; 801 auto ret = appender!string(); 802 foreach(ch; s) 803 ret.put(isIdentChar(ch) ? ch : '_'); 804 return ret.data; 805 }