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