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