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. import std.encoding : sanitize;
  35.  
  36. /// Representing a full project, with a root Package and several dependencies.
  37. class Project {
  38. private {
  39. PackageManager m_packageManager;
  40. Json m_packageSettings;
  41. Package m_rootPackage;
  42. Package[] m_dependencies;
  43. Package[][Package] m_dependees;
  44. SelectedVersions m_selections;
  45. }
  46.  
  47. this(PackageManager package_manager, Path project_path)
  48. {
  49. Package pack;
  50. auto packageFile = Package.findPackageFile(project_path);
  51. if (packageFile.empty) {
  52. logWarn("There was no package description found for the application in '%s'.", project_path.toNativeString());
  53. pack = new Package(null, project_path);
  54. } else {
  55. pack = package_manager.getOrLoadPackage(project_path, packageFile);
  56. }
  57.  
  58. this(package_manager, pack);
  59. }
  60.  
  61. this(PackageManager package_manager, Package pack)
  62. {
  63. m_packageManager = package_manager;
  64. m_rootPackage = pack;
  65. m_packageSettings = Json.emptyObject;
  66.  
  67. try m_packageSettings = jsonFromFile(m_rootPackage.path ~ ".dub/dub.json", true);
  68. catch(Exception t) logDiagnostic("Failed to read .dub/dub.json: %s", t.msg);
  69.  
  70. auto selverfile = m_rootPackage.path ~ SelectedVersions.defaultFile;
  71. if (existsFile(selverfile)) {
  72. try m_selections = new SelectedVersions(selverfile);
  73. catch(Exception e) {
  74. logDiagnostic("A " ~ SelectedVersions.defaultFile ~ " file was not found or failed to load:\n%s", e.msg);
  75. m_selections = new SelectedVersions;
  76. }
  77. } else m_selections = new SelectedVersions;
  78.  
  79. reinit();
  80. }
  81.  
  82. /// Gathers information
  83. @property string info()
  84. const {
  85. if(!m_rootPackage)
  86. return "-Unrecognized application in '"~m_rootPackage.path.toNativeString()~"' (probably no dub.json in this directory)";
  87. string s = "-Application identifier: " ~ m_rootPackage.name;
  88. s ~= "\n" ~ m_rootPackage.generateInfoString();
  89. s ~= "\n-Retrieved dependencies:";
  90. foreach(p; m_dependencies)
  91. s ~= "\n" ~ p.generateInfoString();
  92. return s;
  93. }
  94.  
  95. /// Gets all retrieved packages as a "packageId" = "version" associative array
  96. @property string[string] cachedPackagesIDs() const {
  97. string[string] pkgs;
  98. foreach(p; m_dependencies)
  99. pkgs[p.name] = p.vers;
  100. return pkgs;
  101. }
  102.  
  103. /// List of retrieved dependency Packages
  104. @property const(Package[]) dependencies() const { return m_dependencies; }
  105.  
  106. /// Main package.
  107. @property inout(Package) rootPackage() inout { return m_rootPackage; }
  108.  
  109. /// The versions to use for all dependencies. Call reinit() after changing these.
  110. @property inout(SelectedVersions) selections() inout { return m_selections; }
  111.  
  112. /// Package manager instance used by the project.
  113. @property inout(PackageManager) packageManager() inout { return m_packageManager; }
  114.  
  115. /** Allows iteration of the dependency tree in topological order
  116. */
  117. int delegate(int delegate(ref const Package)) getTopologicalPackageList(bool children_first = false, in Package root_package = null, string[string] configs = null)
  118. const {
  119. const(Package) rootpack = root_package ? root_package : m_rootPackage;
  120.  
  121. int iterator(int delegate(ref const Package) del)
  122. {
  123. int ret = 0;
  124. bool[const(Package)] visited;
  125. void perform_rec(in Package p){
  126. if( p in visited ) return;
  127. visited[p] = true;
  128.  
  129. if( !children_first ){
  130. ret = del(p);
  131. if( ret ) return;
  132. }
  133.  
  134. auto cfg = configs.get(p.name, null);
  135.  
  136. foreach (dn, dv; p.dependencies) {
  137. // filter out dependencies not in the current configuration set
  138. if (!p.hasDependency(dn, cfg)) continue;
  139. auto dependency = getDependency(dn, dv.optional);
  140. if(dependency) perform_rec(dependency);
  141. if( ret ) return;
  142. }
  143.  
  144. if( children_first ){
  145. ret = del(p);
  146. if( ret ) return;
  147. }
  148. }
  149. perform_rec(rootpack);
  150. return ret;
  151. }
  152.  
  153. return &iterator;
  154. }
  155.  
  156. inout(Package) getDependency(string name, bool isOptional)
  157. inout {
  158. foreach(dp; m_dependencies)
  159. if( dp.name == name )
  160. return dp;
  161. if(!isOptional) throw new Exception("Unknown dependency: "~name);
  162. else return null;
  163. }
  164.  
  165. string getDefaultConfiguration(BuildPlatform platform, bool allow_non_library_configs = true)
  166. const {
  167. auto cfgs = getPackageConfigs(platform, null, allow_non_library_configs);
  168. return cfgs[m_rootPackage.name];
  169. }
  170.  
  171. void validate()
  172. {
  173. // some basic package lint
  174. m_rootPackage.warnOnSpecialCompilerFlags();
  175. if (m_rootPackage.name != m_rootPackage.name.toLower()) {
  176. logWarn(`WARNING: DUB package names should always be lower case, please change `
  177. ~ `to {"name": "%s"}. You can use {"targetName": "%s"} to keep the current `
  178. ~ `executable name.`,
  179. m_rootPackage.name.toLower(), m_rootPackage.name);
  180. }
  181.  
  182. foreach (dn, ds; m_rootPackage.dependencies)
  183. if (ds.isExactVersion && ds.version_.isBranch) {
  184. logWarn("WARNING: A deprecated branch based version specification is used "
  185. ~ "for the dependency %s. Please use numbered versions instead. Also "
  186. ~ "note that you can still use the %s file to override a certain "
  187. ~ "dependency to use a branch instead.",
  188. dn, SelectedVersions.defaultFile);
  189. }
  190.  
  191. bool[string] visited;
  192. void validateDependenciesRec(Package pack) {
  193. foreach (name, vspec_; pack.dependencies) {
  194. if (name in visited) continue;
  195. visited[name] = true;
  196.  
  197. auto basename = getBasePackageName(name);
  198. if (m_selections.hasSelectedVersion(basename)) {
  199. auto selver = m_selections.getSelectedVersion(basename);
  200. if (vspec_.merge(selver) == Dependency.invalid) {
  201. logWarn("Selected package %s %s does not match the dependency specification in package %s (%s). Need to \"dub upgrade\"?",
  202. basename, selver, pack.name, vspec_);
  203. }
  204. }
  205.  
  206. auto deppack = getDependency(name, true);
  207. if (deppack) validateDependenciesRec(deppack);
  208. }
  209. }
  210. validateDependenciesRec(m_rootPackage);
  211. }
  212.  
  213. /// Rereads the applications state.
  214. void reinit()
  215. {
  216. m_dependencies = null;
  217. m_packageManager.refresh(false);
  218.  
  219. void collectDependenciesRec(Package pack)
  220. {
  221. logDebug("Collecting dependencies for %s", pack.name);
  222. foreach (name, vspec_; pack.dependencies) {
  223. Dependency vspec = vspec_;
  224. Package p;
  225. if (!vspec.path.empty) {
  226. Path path = vspec.path;
  227. if (!path.absolute) path = pack.path ~ path;
  228. logDiagnostic("Adding local %s", path);
  229. p = m_packageManager.getOrLoadPackage(path);
  230. if (name.canFind(':')) p = m_packageManager.getSubPackage(p, getSubPackageName(name), false);
  231. enforce(p.name == name,
  232. format("Path based dependency %s is referenced with a wrong name: %s vs. %s",
  233. path.toNativeString(), name, p.name));
  234. }
  235.  
  236. if (!p) {
  237. auto basename = getBasePackageName(name);
  238. if (name == m_rootPackage.basePackage.name) {
  239. vspec = Dependency(m_rootPackage.ver);
  240. p = m_rootPackage.basePackage;
  241. } else if (basename == m_rootPackage.basePackage.name) {
  242. vspec = Dependency(m_rootPackage.ver);
  243. try p = m_packageManager.getSubPackage(m_rootPackage.basePackage, getSubPackageName(name), false);
  244. catch (Exception e) {
  245. logDiagnostic("Error getting sub package %s: %s", name, e.msg);
  246. continue;
  247. }
  248. } else {
  249. if (!m_selections.hasSelectedVersion(basename)) {
  250. logDiagnostic("Version selection for dependency %s (%s) of %s is missing.",
  251. basename, name, pack.name);
  252. continue;
  253. }
  254.  
  255. vspec = m_selections.getSelectedVersion(basename);
  256.  
  257. p = m_packageManager.getBestPackage(name, vspec);
  258. }
  259. }
  260.  
  261. if (!p) {
  262. logDiagnostic("Missing dependency %s %s of %s", name, vspec, pack.name);
  263. continue;
  264. }
  265.  
  266. if (!m_dependencies.canFind(p)) {
  267. logDiagnostic("Found dependency %s %s", name, vspec.toString());
  268. m_dependencies ~= p;
  269. p.warnOnSpecialCompilerFlags();
  270. collectDependenciesRec(p);
  271. }
  272.  
  273. m_dependees[p] ~= pack;
  274. //enforce(p !is null, "Failed to resolve dependency "~name~" "~vspec.toString());
  275. }
  276. }
  277. collectDependenciesRec(m_rootPackage);
  278. }
  279.  
  280. /// Returns the applications name.
  281. @property string name() const { return m_rootPackage ? m_rootPackage.name : "app"; }
  282.  
  283. @property string[] configurations() const { return m_rootPackage.configurations; }
  284.  
  285. /// Returns a map with the configuration for all packages in the dependency tree.
  286. string[string] getPackageConfigs(in BuildPlatform platform, string config, bool allow_non_library = true)
  287. const {
  288. struct Vertex { string pack, config; }
  289. struct Edge { size_t from, to; }
  290.  
  291. Vertex[] configs;
  292. Edge[] edges;
  293. string[][string] parents;
  294. parents[m_rootPackage.name] = null;
  295. foreach (p; getTopologicalPackageList())
  296. foreach (d; p.dependencies.byKey)
  297. parents[d] ~= p.name;
  298.  
  299.  
  300. size_t createConfig(string pack, string config) {
  301. foreach (i, v; configs)
  302. if (v.pack == pack && v.config == config)
  303. return i;
  304. logDebug("Add config %s %s", pack, config);
  305. configs ~= Vertex(pack, config);
  306. return configs.length-1;
  307. }
  308.  
  309. bool haveConfig(string pack, string config) {
  310. return configs.any!(c => c.pack == pack && c.config == config);
  311. }
  312.  
  313. size_t createEdge(size_t from, size_t to) {
  314. auto idx = edges.countUntil(Edge(from, to));
  315. if (idx >= 0) return idx;
  316. logDebug("Including %s %s -> %s %s", configs[from].pack, configs[from].config, configs[to].pack, configs[to].config);
  317. edges ~= Edge(from, to);
  318. return edges.length-1;
  319. }
  320.  
  321. void removeConfig(size_t i) {
  322. logDebug("Eliminating config %s for %s", configs[i].config, configs[i].pack);
  323. configs = configs.remove(i);
  324. edges = edges.filter!(e => e.from != i && e.to != i).array();
  325. foreach (ref e; edges) {
  326. if (e.from > i) e.from--;
  327. if (e.to > i) e.to--;
  328. }
  329. }
  330.  
  331. bool isReachable(string pack, string conf) {
  332. if (pack == configs[0].pack && configs[0].config == conf) return true;
  333. foreach (e; edges)
  334. if (configs[e.to].pack == pack && configs[e.to].config == conf)
  335. return true;
  336. return false;
  337. //return (pack == configs[0].pack && conf == configs[0].config) || edges.canFind!(e => configs[e.to].pack == pack && configs[e.to].config == config);
  338. }
  339.  
  340. bool isReachableByAllParentPacks(size_t cidx) {
  341. bool[string] r;
  342. foreach (p; parents[configs[cidx].pack]) r[p] = false;
  343. foreach (e; edges) {
  344. if (e.to != cidx) continue;
  345. if (auto pp = configs[e.from].pack in r) *pp = true;
  346. }
  347. foreach (bool v; r) if (!v) return false;
  348. return true;
  349. }
  350.  
  351. string[] allconfigs_path;
  352. // create a graph of all possible package configurations (package, config) -> (subpackage, subconfig)
  353. void determineAllConfigs(in Package p)
  354. {
  355. auto idx = allconfigs_path.countUntil(p.name);
  356. enforce(idx < 0, format("Detected dependency cycle: %s", (allconfigs_path[idx .. $] ~ p.name).join("->")));
  357. allconfigs_path ~= p.name;
  358. scope (exit) allconfigs_path.length--;
  359.  
  360. // first, add all dependency configurations
  361. foreach (dn; p.dependencies.byKey) {
  362. auto dp = getDependency(dn, true);
  363. if (!dp) continue;
  364. determineAllConfigs(dp);
  365. }
  366.  
  367. // for each configuration, determine the configurations usable for the dependencies
  368. outer: foreach (c; p.getPlatformConfigurations(platform, p is m_rootPackage && allow_non_library)) {
  369. string[][string] depconfigs;
  370. foreach (dn; p.dependencies.byKey) {
  371. auto dp = getDependency(dn, true);
  372. if (!dp) continue;
  373.  
  374. string[] cfgs;
  375. auto subconf = p.getSubConfiguration(c, dp, platform);
  376. if (!subconf.empty) cfgs = [subconf];
  377. else cfgs = dp.getPlatformConfigurations(platform);
  378. cfgs = cfgs.filter!(c => haveConfig(dn, c)).array;
  379.  
  380. // if no valid configuration was found for a dependency, don't include the
  381. // current configuration
  382. if (!cfgs.length) {
  383. logDebug("Skip %s %s (missing configuration for %s)", p.name, c, dp.name);
  384. continue outer;
  385. }
  386. depconfigs[dn] = cfgs;
  387. }
  388.  
  389. // add this configuration to the graph
  390. size_t cidx = createConfig(p.name, c);
  391. foreach (dn; p.dependencies.byKey)
  392. foreach (sc; depconfigs.get(dn, null))
  393. createEdge(cidx, createConfig(dn, sc));
  394. }
  395. }
  396. if (config.length) createConfig(m_rootPackage.name, config);
  397. determineAllConfigs(m_rootPackage);
  398.  
  399. // successively remove configurations until only one configuration per package is left
  400. bool changed;
  401. do {
  402. // remove all configs that are not reachable by all parent packages
  403. changed = false;
  404. for (size_t i = 0; i < configs.length; ) {
  405. if (!isReachableByAllParentPacks(i)) {
  406. logDebug("NOT REACHABLE by (%s):", parents[configs[i].pack]);
  407. removeConfig(i);
  408. changed = true;
  409. } else i++;
  410. }
  411.  
  412. // when all edges are cleaned up, pick one package and remove all but one config
  413. if (!changed) {
  414. foreach (p; getTopologicalPackageList()) {
  415. size_t cnt = 0;
  416. for (size_t i = 0; i < configs.length; ) {
  417. if (configs[i].pack == p.name) {
  418. if (++cnt > 1) {
  419. logDebug("NON-PRIMARY:");
  420. removeConfig(i);
  421. } else i++;
  422. } else i++;
  423. }
  424. if (cnt > 1) {
  425. changed = true;
  426. break;
  427. }
  428. }
  429. }
  430. } while (changed);
  431.  
  432. // print out the resulting tree
  433. foreach (e; edges) logDebug(" %s %s -> %s %s", configs[e.from].pack, configs[e.from].config, configs[e.to].pack, configs[e.to].config);
  434.  
  435. // return the resulting configuration set as an AA
  436. string[string] ret;
  437. foreach (c; configs) {
  438. 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]));
  439. logDebug("Using configuration '%s' for %s", c.config, c.pack);
  440. ret[c.pack] = c.config;
  441. }
  442.  
  443. // check for conflicts (packages missing in the final configuration graph)
  444. void checkPacksRec(in Package pack) {
  445. auto pc = pack.name in ret;
  446. enforce(pc !is null, "Could not resolve configuration for package "~pack.name);
  447. foreach (p, dep; pack.getDependencies(*pc)) {
  448. auto deppack = getDependency(p, dep.optional);
  449. if (deppack) checkPacksRec(deppack);
  450. }
  451. }
  452. checkPacksRec(m_rootPackage);
  453.  
  454. return ret;
  455. }
  456.  
  457. /**
  458. * Fills dst with values from this project.
  459. *
  460. * dst gets initialized according to the given platform and config.
  461. *
  462. * Params:
  463. * dst = The BuildSettings struct to fill with data.
  464. * platform = The platform to retrieve the values for.
  465. * config = Values of the given configuration will be retrieved.
  466. * root_package = If non null, use it instead of the project's real root package.
  467. * shallow = If true, collects only build settings for the main package (including inherited settings) and doesn't stop on target type none and sourceLibrary.
  468. */
  469. void addBuildSettings(ref BuildSettings dst, in BuildPlatform platform, string config, in Package root_package = null, bool shallow = false)
  470. const {
  471. auto configs = getPackageConfigs(platform, config);
  472.  
  473. foreach (pkg; this.getTopologicalPackageList(false, root_package, configs)) {
  474. auto pkg_path = pkg.path.toNativeString();
  475. dst.addVersions(["Have_" ~ stripDlangSpecialChars(pkg.name)]);
  476.  
  477. assert(pkg.name in configs, "Missing configuration for "~pkg.name);
  478. logDebug("Gathering build settings for %s (%s)", pkg.name, configs[pkg.name]);
  479.  
  480. auto psettings = pkg.getBuildSettings(platform, configs[pkg.name]);
  481. if (psettings.targetType != TargetType.none) {
  482. if (shallow && pkg !is m_rootPackage)
  483. psettings.sourceFiles = null;
  484. processVars(dst, this, pkg, psettings);
  485. if (psettings.importPaths.empty)
  486. 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]);
  487. if (psettings.mainSourceFile.empty && pkg is m_rootPackage && psettings.targetType == TargetType.executable)
  488. 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);
  489. }
  490. if (pkg is m_rootPackage) {
  491. if (!shallow) {
  492. enforce(psettings.targetType != TargetType.none, "Main package has target type \"none\" - stopping build.");
  493. enforce(psettings.targetType != TargetType.sourceLibrary, "Main package has target type \"sourceLibrary\" which generates no target - stopping build.");
  494. }
  495. dst.targetType = psettings.targetType;
  496. dst.targetPath = psettings.targetPath;
  497. dst.targetName = psettings.targetName;
  498. if (!psettings.workingDirectory.empty)
  499. dst.workingDirectory = processVars(psettings.workingDirectory, this, pkg, true);
  500. if (psettings.mainSourceFile.length)
  501. dst.mainSourceFile = processVars(psettings.mainSourceFile, this, pkg, true);
  502. }
  503. }
  504.  
  505. // always add all version identifiers of all packages
  506. foreach (pkg; this.getTopologicalPackageList(false, null, configs)) {
  507. auto psettings = pkg.getBuildSettings(platform, configs[pkg.name]);
  508. dst.addVersions(psettings.versions);
  509. }
  510. }
  511.  
  512. void addBuildTypeSettings(ref BuildSettings dst, in BuildPlatform platform, string build_type)
  513. {
  514. bool usedefflags = !(dst.requirements & BuildRequirements.noDefaultFlags);
  515. if (usedefflags) {
  516. BuildSettings btsettings;
  517. m_rootPackage.addBuildTypeSettings(btsettings, platform, build_type);
  518. processVars(dst, this, m_rootPackage, btsettings);
  519. }
  520. }
  521.  
  522. /// Determines if the given dependency is already indirectly referenced by other dependencies of pack.
  523. bool isRedundantDependency(in Package pack, in Package dependency)
  524. const {
  525. foreach (dep; pack.dependencies.byKey) {
  526. auto dp = getDependency(dep, true);
  527. if (!dp) continue;
  528. if (dp is dependency) continue;
  529. foreach (ddp; getTopologicalPackageList(false, dp))
  530. if (ddp is dependency) return true;
  531. }
  532. return false;
  533. }
  534.  
  535. /*bool iterateDependencies(bool delegate(Package pack, string dep_name, Dependency dep_spec) del)
  536. {
  537. bool all_found = true;
  538.  
  539. bool[string] visited;
  540. void iterate(Package pack)
  541. {
  542. if (pack.name in visited) return;
  543. visited[pack.name] = true;
  544.  
  545. foreach (dn, ds; pack.dependencies) {
  546. auto dep = del(pack, dn, ds);
  547. if (dep) iterateDependencies(dep);
  548. else all_found = false;
  549. }
  550. }
  551.  
  552. return all_found;
  553. }*/
  554.  
  555. /// Outputs a JSON description of the project, including its deoendencies.
  556. void describe(ref Json dst, BuildPlatform platform, string config)
  557. {
  558. dst.mainPackage = m_rootPackage.name; // deprecated
  559. dst.rootPackage = m_rootPackage.name;
  560.  
  561. auto configs = getPackageConfigs(platform, config);
  562.  
  563. auto mp = Json.emptyObject;
  564. m_rootPackage.describe(mp, platform, config);
  565. dst.packages = Json([mp]);
  566.  
  567. foreach (dep; m_dependencies) {
  568. auto dp = Json.emptyObject;
  569. dep.describe(dp, platform, configs[dep.name]);
  570. dst.packages = dst.packages.get!(Json[]) ~ dp;
  571. }
  572. }
  573.  
  574. void saveSelections()
  575. {
  576. assert(m_selections !is null, "Cannot save selections for non-disk based project (has no selections).");
  577. if (m_selections.hasSelectedVersion(m_rootPackage.basePackage.name))
  578. m_selections.deselectVersion(m_rootPackage.basePackage.name);
  579.  
  580. auto path = m_rootPackage.path ~ SelectedVersions.defaultFile;
  581. if (m_selections.dirty || !existsFile(path))
  582. m_selections.save(path);
  583. }
  584.  
  585. bool isUpgradeCacheUpToDate()
  586. {
  587. try {
  588. auto datestr = m_packageSettings["dub"].opt!(Json[string]).get("lastUpgrade", Json("")).get!string;
  589. if (!datestr.length) return false;
  590. auto date = SysTime.fromISOExtString(datestr);
  591. if ((Clock.currTime() - date) > 1.days) return false;
  592. return true;
  593. } catch (Exception t) {
  594. logDebug("Failed to get the last upgrade time: %s", t.msg);
  595. return false;
  596. }
  597. }
  598.  
  599. Dependency[string] getUpgradeCache()
  600. {
  601. try {
  602. Dependency[string] ret;
  603. foreach (string p, d; m_packageSettings["dub"].opt!(Json[string]).get("cachedUpgrades", Json.emptyObject))
  604. ret[p] = SelectedVersions.dependencyFromJson(d);
  605. return ret;
  606. } catch (Exception t) {
  607. logDebug("Failed to get cached upgrades: %s", t.msg);
  608. return null;
  609. }
  610. }
  611.  
  612. void setUpgradeCache(Dependency[string] versions)
  613. {
  614. logDebug("markUpToDate");
  615. Json create(ref Json json, string object) {
  616. if( object !in json ) json[object] = Json.emptyObject;
  617. return json[object];
  618. }
  619. create(m_packageSettings, "dub");
  620. m_packageSettings["dub"]["lastUpgrade"] = Clock.currTime().toISOExtString();
  621.  
  622. create(m_packageSettings["dub"], "cachedUpgrades");
  623. foreach (p, d; versions)
  624. m_packageSettings["dub"]["cachedUpgrades"][p] = SelectedVersions.dependencyToJson(d);
  625.  
  626. writeDubJson();
  627. }
  628.  
  629. private void writeDubJson() {
  630. // don't bother to write an empty file
  631. if( m_packageSettings.length == 0 ) return;
  632.  
  633. try {
  634. logDebug("writeDubJson");
  635. auto dubpath = m_rootPackage.path~".dub";
  636. if( !exists(dubpath.toNativeString()) ) mkdir(dubpath.toNativeString());
  637. auto dstFile = openFile((dubpath~"dub.json").toString(), FileMode.CreateTrunc);
  638. scope(exit) dstFile.close();
  639. dstFile.writePrettyJsonString(m_packageSettings);
  640. } catch( Exception e ){
  641. logWarn("Could not write .dub/dub.json.");
  642. }
  643. }
  644. }
  645.  
  646. /// Actions to be performed by the dub
  647. struct Action {
  648. enum Type {
  649. fetch,
  650. remove,
  651. conflict,
  652. failure
  653. }
  654.  
  655. immutable {
  656. Type type;
  657. string packageId;
  658. PlacementLocation location;
  659. Dependency vers;
  660. Version existingVersion;
  661. }
  662. const Package pack;
  663. const Dependency[string] issuer;
  664.  
  665. static Action get(string pkg, PlacementLocation location, in Dependency dep, Dependency[string] context, Version old_version = Version.UNKNOWN)
  666. {
  667. return Action(Type.fetch, pkg, location, dep, context, old_version);
  668. }
  669.  
  670. static Action remove(Package pkg, Dependency[string] context)
  671. {
  672. return Action(Type.remove, pkg, context);
  673. }
  674.  
  675. static Action conflict(string pkg, in Dependency dep, Dependency[string] context)
  676. {
  677. return Action(Type.conflict, pkg, PlacementLocation.user, dep, context);
  678. }
  679.  
  680. static Action failure(string pkg, in Dependency dep, Dependency[string] context)
  681. {
  682. return Action(Type.failure, pkg, PlacementLocation.user, dep, context);
  683. }
  684.  
  685. private this(Type id, string pkg, PlacementLocation location, in Dependency d, Dependency[string] issue, Version existing_version = Version.UNKNOWN)
  686. {
  687. this.type = id;
  688. this.packageId = pkg;
  689. this.location = location;
  690. this.vers = d;
  691. this.issuer = issue;
  692. this.existingVersion = existing_version;
  693. }
  694.  
  695. private this(Type id, Package pkg, Dependency[string] issue)
  696. {
  697. pack = pkg;
  698. type = id;
  699. packageId = pkg.name;
  700. vers = cast(immutable)Dependency(pkg.ver);
  701. issuer = issue;
  702. }
  703.  
  704. string toString() const {
  705. return to!string(type) ~ ": " ~ packageId ~ ", " ~ to!string(vers);
  706. }
  707. }
  708.  
  709.  
  710. /// Indicates where a package has been or should be placed to.
  711. enum PlacementLocation {
  712. /// Packages retrived with 'local' will be placed in the current folder
  713. /// using the package name as destination.
  714. local,
  715. /// Packages with 'userWide' will be placed in a folder accessible by
  716. /// all of the applications from the current user.
  717. user,
  718. /// Packages retrieved with 'systemWide' will be placed in a shared folder,
  719. /// which can be accessed by all users of the system.
  720. system
  721. }
  722.  
  723. /// The default placement location of fetched packages. Can be changed by --local or --system.
  724. auto defaultPlacementLocation = PlacementLocation.user;
  725.  
  726. void processVars(ref BuildSettings dst, in Project project, in Package pack, BuildSettings settings, bool include_target_settings = false)
  727.  
  728. {
  729. dst.addDFlags(processVars(project, pack, settings.dflags));
  730. dst.addLFlags(processVars(project, pack, settings.lflags));
  731. dst.addLibs(processVars(project, pack, settings.libs));
  732. dst.addSourceFiles(processVars(project, pack, settings.sourceFiles, true));
  733. dst.addImportFiles(processVars(project, pack, settings.importFiles, true));
  734. dst.addStringImportFiles(processVars(project, pack, settings.stringImportFiles, true));
  735. dst.addCopyFiles(processVars(project, pack, settings.copyFiles, true));
  736. dst.addVersions(processVars(project, pack, settings.versions));
  737. dst.addDebugVersions(processVars(project, pack, settings.debugVersions));
  738. dst.addImportPaths(processVars(project, pack, settings.importPaths, true));
  739. dst.addStringImportPaths(processVars(project, pack, settings.stringImportPaths, true));
  740. dst.addPreGenerateCommands(processVars(project, pack, settings.preGenerateCommands));
  741. dst.addPostGenerateCommands(processVars(project, pack, settings.postGenerateCommands));
  742. dst.addPreBuildCommands(processVars(project, pack, settings.preBuildCommands));
  743. dst.addPostBuildCommands(processVars(project, pack, settings.postBuildCommands));
  744. dst.addRequirements(settings.requirements);
  745. dst.addOptions(settings.options);
  746.  
  747. if (include_target_settings) {
  748. dst.targetType = settings.targetType;
  749. dst.targetPath = processVars(settings.targetPath, project, pack, true);
  750. dst.targetName = settings.targetName;
  751. if (!settings.workingDirectory.empty)
  752. dst.workingDirectory = processVars(settings.workingDirectory, project, pack, true);
  753. if (settings.mainSourceFile.length)
  754. dst.mainSourceFile = processVars(settings.mainSourceFile, project, pack, true);
  755. }
  756. }
  757.  
  758. private string[] processVars(in Project project, in Package pack, string[] vars, bool are_paths = false)
  759. {
  760. auto ret = appender!(string[])();
  761. processVars(ret, project, pack, vars, are_paths);
  762. return ret.data;
  763.  
  764. }
  765. private void processVars(ref Appender!(string[]) dst, in Project project, in Package pack, string[] vars, bool are_paths = false)
  766. {
  767. foreach (var; vars) dst.put(processVars(var, project, pack, are_paths));
  768. }
  769.  
  770. private string processVars(string var, in Project project, in Package pack, bool is_path)
  771. {
  772. auto idx = std.string.indexOf(var, '$');
  773. if (idx >= 0) {
  774. auto vres = appender!string();
  775. while (idx >= 0) {
  776. if (idx+1 >= var.length) break;
  777. if (var[idx+1] == '$') {
  778. vres.put(var[0 .. idx+1]);
  779. var = var[idx+2 .. $];
  780. } else {
  781. vres.put(var[0 .. idx]);
  782. var = var[idx+1 .. $];
  783.  
  784. size_t idx2 = 0;
  785. while( idx2 < var.length && isIdentChar(var[idx2]) ) idx2++;
  786. auto varname = var[0 .. idx2];
  787. var = var[idx2 .. $];
  788.  
  789. vres.put(getVariable(varname, project, pack));
  790. }
  791. idx = std.string.indexOf(var, '$');
  792. }
  793. vres.put(var);
  794. var = vres.data;
  795. }
  796. if (is_path) {
  797. auto p = Path(var);
  798. if (!p.absolute) {
  799. logDebug("Fixing relative path: %s ~ %s", pack.path.toNativeString(), p.toNativeString());
  800. return (pack.path ~ p).toNativeString();
  801. } else return p.toNativeString();
  802. } else return var;
  803. }
  804.  
  805. private string getVariable(string name, in Project project, in Package pack)
  806. {
  807. if (name == "PACKAGE_DIR") return pack.path.toNativeString();
  808. if (name == "ROOT_PACKAGE_DIR") return project.rootPackage.path.toNativeString();
  809.  
  810. if (name.endsWith("_PACKAGE_DIR")) {
  811. auto pname = name[0 .. $-12];
  812. foreach (prj; project.getTopologicalPackageList())
  813. if (prj.name.toUpper().replace("-", "_") == pname)
  814. return prj.path.toNativeString();
  815. }
  816.  
  817. if (auto envvar = environment.get(name)) return envvar;
  818.  
  819. throw new Exception("Invalid variable: "~name);
  820. }
  821.  
  822. private bool isIdentChar(dchar ch)
  823. {
  824. return ch >= 'A' && ch <= 'Z' || ch >= 'a' && ch <= 'z' || ch >= '0' && ch <= '9' || ch == '_';
  825. }
  826.  
  827. string stripDlangSpecialChars(string s)
  828. {
  829. import std.array;
  830. import std.uni;
  831. auto ret = appender!string();
  832. foreach(ch; s)
  833. ret.put(isIdentChar(ch) ? ch : '_');
  834. return ret.data;
  835. }
  836.  
  837. final class SelectedVersions {
  838. private struct Selected {
  839. Dependency dep;
  840. //Dependency[string] packages;
  841. }
  842. private {
  843. enum FileVersion = 1;
  844. Selected[string] m_selections;
  845. bool m_dirty = false; // has changes since last save
  846. }
  847.  
  848. enum defaultFile = "dub.selections.json";
  849.  
  850. this() {}
  851.  
  852. this(Json data)
  853. {
  854. deserialize(data);
  855. m_dirty = false;
  856. }
  857.  
  858. this(Path path)
  859. {
  860. auto json = jsonFromFile(path);
  861. deserialize(json);
  862. m_dirty = false;
  863. }
  864.  
  865. @property string[] selectedPackages() const { return m_selections.keys; }
  866.  
  867. @property bool dirty() const { return m_dirty; }
  868.  
  869. void clear()
  870. {
  871. m_selections = null;
  872. m_dirty = true;
  873. }
  874.  
  875. void set(SelectedVersions versions)
  876. {
  877. m_selections = versions.m_selections.dup;
  878. m_dirty = true;
  879. }
  880.  
  881. void selectVersion(string package_id, Version version_)
  882. {
  883. if (auto ps = package_id in m_selections) {
  884. if (ps.dep == Dependency(version_))
  885. return;
  886. }
  887. m_selections[package_id] = Selected(Dependency(version_)/*, issuer*/);
  888. m_dirty = true;
  889. }
  890.  
  891. void selectVersion(string package_id, Path path)
  892. {
  893. if (auto ps = package_id in m_selections) {
  894. if (ps.dep == Dependency(path))
  895. return;
  896. }
  897. m_selections[package_id] = Selected(Dependency(path));
  898. m_dirty = true;
  899. }
  900.  
  901. void deselectVersion(string package_id)
  902. {
  903. m_selections.remove(package_id);
  904. m_dirty = true;
  905. }
  906.  
  907. bool hasSelectedVersion(string packageId)
  908. const {
  909. return (packageId in m_selections) !is null;
  910. }
  911.  
  912. Dependency getSelectedVersion(string packageId)
  913. const {
  914. enforce(hasSelectedVersion(packageId));
  915. return m_selections[packageId].dep;
  916. }
  917.  
  918. void save(Path path)
  919. {
  920. Json json = serialize();
  921. auto file = openFile(path, FileMode.CreateTrunc);
  922. scope(exit) file.close();
  923. file.writePrettyJsonString(json);
  924. file.put('\n');
  925. m_dirty = false;
  926. }
  927.  
  928. static Json dependencyToJson(Dependency d)
  929. {
  930. if (d.path.empty) return Json(d.version_.toString());
  931. else return serializeToJson(["path": d.path.toString()]);
  932. }
  933.  
  934. static Dependency dependencyFromJson(Json j)
  935. {
  936. if (j.type == Json.Type.string)
  937. return Dependency(Version(j.get!string));
  938. else if (j.type == Json.Type.object)
  939. return Dependency(Path(j.path.get!string));
  940. else throw new Exception(format("Unexpected type for dependency: %s", j.type));
  941. }
  942.  
  943. Json serialize()
  944. const {
  945. Json json = serializeToJson(m_selections);
  946. Json serialized = Json.emptyObject;
  947. serialized.fileVersion = FileVersion;
  948. serialized.versions = Json.emptyObject;
  949. foreach (p, v; m_selections)
  950. serialized.versions[p] = dependencyToJson(v.dep);
  951. return serialized;
  952. }
  953.  
  954. private void deserialize(Json json)
  955. {
  956. enforce(cast(int)json["fileVersion"] == FileVersion, "Mismatched dub.select.json version: " ~ to!string(cast(int)json["fileVersion"]) ~ "vs. " ~to!string(FileVersion));
  957. clear();
  958. scope(failure) clear();
  959. foreach (string p, v; json.versions)
  960. m_selections[p] = Selected(dependencyFromJson(v));
  961. }
  962. }