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