Newer
Older
dub_jkp / source / dub / dub.d
  1. /**
  2. A package manager.
  3.  
  4. Copyright: © 2012 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.dub;
  9.  
  10. import dub.compilers.compiler;
  11. import dub.dependency;
  12. import dub.installation;
  13. import dub.utils;
  14. import dub.registry;
  15. import dub.package_;
  16. import dub.packagemanager;
  17. import dub.packagesupplier;
  18. import dub.project;
  19. import dub.generators.generator;
  20.  
  21. import vibecompat.core.file;
  22. import vibecompat.core.log;
  23. import vibecompat.data.json;
  24. import vibecompat.inet.url;
  25.  
  26. // todo: cleanup imports.
  27. import std.algorithm;
  28. import std.array;
  29. import std.conv;
  30. import std.datetime;
  31. import std.exception;
  32. import std.file;
  33. import std.string;
  34. import std.typecons;
  35. import std.zip;
  36. import stdx.process;
  37.  
  38.  
  39.  
  40. /// The default supplier for packages, which is the registry
  41. /// hosted by vibed.org.
  42. PackageSupplier defaultPackageSupplier() {
  43. Url url = Url.parse("http://registry.vibed.org/");
  44. logDebug("Using the registry from %s", url);
  45. return new RegistryPS(url);
  46. }
  47.  
  48. /// The Dub class helps in getting the applications
  49. /// dependencies up and running. An instance manages one application.
  50. class Dub {
  51. private {
  52. Path m_cwd, m_tempPath;
  53. Path m_root;
  54. PackageSupplier m_packageSupplier;
  55. Path m_userDubPath, m_systemDubPath;
  56. Json m_systemConfig, m_userConfig;
  57. PackageManager m_packageManager;
  58. Project m_project;
  59. }
  60.  
  61. /// Initiales the package manager for the vibe application
  62. /// under root.
  63. this(PackageSupplier ps = defaultPackageSupplier())
  64. {
  65. m_cwd = Path(getcwd());
  66.  
  67. version(Windows){
  68. m_systemDubPath = Path(environment.get("ProgramData")) ~ "dub/";
  69. m_userDubPath = Path(environment.get("APPDATA")) ~ "dub/";
  70. m_tempPath = Path(environment.get("TEMP"));
  71. } else version(Posix){
  72. m_systemDubPath = Path("/var/lib/dub/");
  73. m_userDubPath = Path(environment.get("HOME")) ~ ".dub/";
  74. m_tempPath = Path("/tmp");
  75. }
  76. m_userConfig = jsonFromFile(m_userDubPath ~ "settings.json", true);
  77. m_systemConfig = jsonFromFile(m_systemDubPath ~ "settings.json", true);
  78.  
  79. m_packageSupplier = ps;
  80. m_packageManager = new PackageManager(m_systemDubPath ~ "packages/", m_userDubPath ~ "packages/");
  81. }
  82.  
  83. /// Returns the name listed in the package.json of the current
  84. /// application.
  85. @property string projectName() const { return m_project.name; }
  86.  
  87. @property Path projectPath() const { return m_root; }
  88.  
  89. @property string[] configurations() const { return m_project.configurations; }
  90.  
  91. @property inout(PackageManager) packageManager() inout { return m_packageManager; }
  92.  
  93. @property Path binaryPath() const { return m_project.binaryPath; }
  94.  
  95. void loadPackageFromCwd()
  96. {
  97. loadPackage(m_cwd);
  98. }
  99.  
  100. void loadPackage(Path path)
  101. {
  102. m_root = path;
  103. m_packageManager.projectPackagePath = m_root ~ ".dub/packages/";
  104. m_project = new Project(m_packageManager, m_root);
  105. }
  106.  
  107. string getDefaultConfiguration(BuildPlatform platform) const { return m_project.getDefaultConfiguration(platform); }
  108.  
  109. /// Performs installation and uninstallation as necessary for
  110. /// the application.
  111. /// @param options bit combination of UpdateOptions
  112. bool update(UpdateOptions options) {
  113. Action[] actions = m_project.determineActions(m_packageSupplier, options);
  114. if( actions.length == 0 ) return true;
  115.  
  116. logInfo("The following changes could be performed:");
  117. bool conflictedOrFailed = false;
  118. foreach(Action a; actions) {
  119. logInfo("%s %s %s, %s", capitalize(to!string(a.type)), a.packageId, a.vers, a.location);
  120. if( a.type == Action.Type.conflict || a.type == Action.Type.failure ) {
  121. logInfo("Issued by: ");
  122. conflictedOrFailed = true;
  123. foreach(string pkg, d; a.issuer)
  124. logInfo(" "~pkg~": %s", d);
  125. }
  126. }
  127.  
  128. if( conflictedOrFailed || options & UpdateOptions.JustAnnotate )
  129. return conflictedOrFailed;
  130.  
  131. // Uninstall first
  132.  
  133. // ??
  134. // foreach(Action a ; filter!((Action a) => a.type == Action.Type.Uninstall)(actions))
  135. // uninstall(a.packageId);
  136. // foreach(Action a; filter!((Action a) => a.type == Action.Type.InstallUpdate)(actions))
  137. // install(a.packageId, a.vers);
  138. foreach(Action a; actions)
  139. if(a.type == Action.Type.uninstall){
  140. assert(a.pack !is null, "No package specified for uninstall.");
  141. uninstall(a.pack);
  142. }
  143. foreach(Action a; actions)
  144. if(a.type == Action.Type.install)
  145. install(a.packageId, a.vers, a.location);
  146.  
  147. m_project.reinit();
  148. Action[] newActions = m_project.determineActions(m_packageSupplier, 0);
  149. if(newActions.length > 0) {
  150. logInfo("There are still some actions to perform:");
  151. foreach(Action a; newActions)
  152. logInfo("%s", a);
  153. }
  154. else
  155. logInfo("You are up to date");
  156.  
  157. return newActions.length == 0;
  158. }
  159.  
  160. /// Generate project files for a specified IDE.
  161. /// Any existing project files will be overridden.
  162. void generateProject(string ide, GeneratorSettings settings) {
  163. auto generator = createProjectGenerator(ide, m_project, m_packageManager);
  164. generator.generateProject(settings);
  165. }
  166. /// Creates a zip from the application.
  167. void createZip(string zipFile) {
  168. m_project.createZip(zipFile);
  169. }
  170.  
  171.  
  172. /// Gets all installed packages as a "packageId" = "version" associative array
  173. string[string] installedPackages() const { return m_project.installedPackagesIDs(); }
  174.  
  175. /// Installs the package matching the dependency into the application.
  176. Package install(string packageId, const Dependency dep, InstallLocation location = InstallLocation.projectLocal)
  177. {
  178. auto pinfo = m_packageSupplier.packageJson(packageId, dep);
  179. string ver = pinfo["version"].get!string;
  180.  
  181. if( auto pack = m_packageManager.getPackage(packageId, ver, location) ){
  182. logInfo("Package %s %s (%s) is already installed with the latest version, skipping upgrade.",
  183. packageId, ver, location);
  184. return pack;
  185. }
  186.  
  187. logInfo("Downloading %s %s...", packageId, ver);
  188.  
  189. logDebug("Acquiring package zip file");
  190. auto dload = m_root ~ ".dub/temp/downloads";
  191. auto tempfname = packageId ~ "-" ~ (ver.startsWith('~') ? ver[1 .. $] : ver) ~ ".zip";
  192. auto tempFile = m_tempPath ~ tempfname;
  193. string sTempFile = tempFile.toNativeString();
  194. if(exists(sTempFile)) remove(sTempFile);
  195. m_packageSupplier.storePackage(tempFile, packageId, dep); // Q: continue on fail?
  196. scope(exit) remove(sTempFile);
  197.  
  198. logInfo("Installing %s %s...", packageId, ver);
  199. return m_packageManager.install(tempFile, pinfo, location);
  200. }
  201.  
  202. /// Uninstalls a given package from the list of installed modules.
  203. /// @removeFromApplication: if true, this will also remove an entry in the
  204. /// list of dependencies in the application's package.json
  205. void uninstall(in Package pack)
  206. {
  207. logInfo("Uninstalling %s in %s", pack.name, pack.path.toNativeString());
  208. m_packageManager.uninstall(pack);
  209. }
  210.  
  211. /// @see uninstall(string, string, InstallLocation)
  212. immutable string UninstallVersionWildcard = "*";
  213.  
  214. /// This will uninstall a given package with a specified version from the
  215. /// location.
  216. /// It will remove at most one package, unless @param version_ is
  217. /// specified as wildcard "*".
  218. /// @param package_id Package to be removed
  219. /// @param version_ Identifying a version or a wild card. An empty string
  220. /// may be passed into. In this case the package will be removed from the
  221. /// location, if there is only one version installed. This will throw an
  222. /// exception, if there are multiple versions installed.
  223. /// Note: as wildcard string only "*" is supported.
  224. /// @param location_
  225. void uninstall(string package_id, string version_, InstallLocation location_) {
  226. enforce(!package_id.empty);
  227. Package[] packages;
  228. const bool wildcardOrEmpty = version_ == UninstallVersionWildcard || version_.empty;
  229. if(location_ == InstallLocation.local) {
  230. // Try folder named like the package_id in the cwd.
  231. try {
  232. Package pack = new Package(InstallLocation.local, Path(package_id));
  233. if(!wildcardOrEmpty && to!string(pack.vers) != version_) {
  234. logError("Installed package is of different version, uninstallation aborted.");
  235. logError("Installed: %s, provided %s@", pack.vers, version_);
  236. throw new Exception("Found package locally, but the versions don't match!");
  237. }
  238. packages ~= pack;
  239. } catch {/* noop */}
  240. } else {
  241. // Use package manager
  242. foreach(pack; m_packageManager.getPackageIterator(package_id)){
  243. if( pack.installLocation == location_ && (wildcardOrEmpty || pack.vers == version_ )) {
  244. packages ~= pack;
  245. }
  246. }
  247. }
  248.  
  249. if(packages.empty) {
  250. logError("Cannot find package to uninstall. (id:%s, version:%s, location:%s)", package_id, version_, location_);
  251. return;
  252. }
  253.  
  254. if(version_.empty && packages.length > 1) {
  255. logError("Cannot uninstall package '%s', there multiple possibilities at location '%s'.", package_id, location_);
  256. logError("Installed versions:");
  257. foreach(pack; packages)
  258. logError(to!string(pack.vers()));
  259. throw new Exception("Failed to uninstall package.");
  260. }
  261.  
  262. logTrace("Uninstalling %s packages.", packages.length);
  263. foreach(pack; packages) {
  264. try {
  265. uninstall(pack);
  266. logInfo("Uninstalled %s, version %s.", package_id, pack.vers);
  267. }
  268. catch logError("Failed to uninstall %s, version %s. Continuing with other packages (if any).", package_id, pack.vers);
  269. }
  270. }
  271.  
  272. void addLocalPackage(string path, string ver, bool system)
  273. {
  274. auto abs_path = Path(path);
  275. if( !abs_path.absolute ) abs_path = m_cwd ~ abs_path;
  276. m_packageManager.addLocalPackage(abs_path, Version(ver), system ? LocalPackageType.system : LocalPackageType.user);
  277. }
  278.  
  279. void removeLocalPackage(string path, bool system)
  280. {
  281. auto abs_path = Path(path);
  282. if( !abs_path.absolute ) abs_path = m_cwd ~ abs_path;
  283. m_packageManager.removeLocalPackage(abs_path, system ? LocalPackageType.system : LocalPackageType.user);
  284. }
  285.  
  286. void createEmptyPackage(Path path)
  287. {
  288. if( !path.absolute() ) path = m_cwd ~ path;
  289. path.normalize();
  290.  
  291. //Check to see if a target directory needs to be created
  292. if( !path.empty ){
  293. if( !existsFile(path) )
  294. createDirectory(path);
  295. }
  296.  
  297. //Make sure we do not overwrite anything accidentally
  298. if( existsFile(path ~ PackageJsonFilename) ||
  299. existsFile(path ~ "source") ||
  300. existsFile(path ~ "views") ||
  301. existsFile(path ~ "public") )
  302. {
  303. throw new Exception("The current directory is not empty.\n");
  304. }
  305.  
  306. //raw strings must be unindented.
  307. immutable packageJson =
  308. `{
  309. "name": "`~(path.empty ? "my-project" : path.head.toString())~`",
  310. "description": "An example project skeleton",
  311. "homepage": "http://example.org",
  312. "copyright": "Copyright © 2000, Your Name",
  313. "authors": [
  314. "Your Name"
  315. ],
  316. "dependencies": {
  317. }
  318. }
  319. `;
  320. immutable appFile =
  321. `import std.stdio;
  322.  
  323. void main()
  324. {
  325. writeln("Edit source/app.d to start your project.");
  326. }
  327. `;
  328.  
  329. //Create the common directories.
  330. createDirectory(path ~ "source");
  331. createDirectory(path ~ "views");
  332. createDirectory(path ~ "public");
  333.  
  334. //Create the common files.
  335. openFile(path ~ PackageJsonFilename, FileMode.Append).write(packageJson);
  336. openFile(path ~ "source/app.d", FileMode.Append).write(appFile);
  337.  
  338. //Act smug to the user.
  339. logInfo("Successfully created an empty project in '"~path.toNativeString()~"'.");
  340. }
  341.  
  342. void runDdox()
  343. {
  344. auto ddox_pack = m_packageManager.getBestPackage("ddox", ">=0.0.0");
  345. if( !ddox_pack ){
  346. logInfo("DDOX is not installed, performing user wide installation.");
  347. ddox_pack = install("ddox", new Dependency(">=0.0.0"), InstallLocation.userWide);
  348. }
  349.  
  350. version(Windows) auto ddox_exe = "ddox.exe";
  351. else auto ddox_exe = "ddox";
  352.  
  353. if( !existsFile(ddox_pack.path~ddox_exe) ){
  354. logInfo("DDOX in %s is not built, performing build now.", ddox_pack.path.toNativeString());
  355.  
  356. auto ddox_dub = new Dub(m_packageSupplier);
  357. ddox_dub.loadPackage(ddox_pack.path);
  358.  
  359. GeneratorSettings settings;
  360. settings.compilerBinary = "dmd";
  361. settings.compiler = getCompiler(settings.compilerBinary);
  362. settings.platform = settings.compiler.determinePlatform(settings.buildSettings, settings.compilerBinary);
  363. settings.buildType = "debug";
  364. ddox_dub.generateProject("build", settings);
  365.  
  366. //runCommands(["cd "~ddox_pack.path.toNativeString()~" && dub build -v"]);
  367. }
  368.  
  369. auto p = ddox_pack.path;
  370. p.endsWithSlash = true;
  371. auto dub_path = p.toNativeString();
  372.  
  373. string[] commands;
  374. commands ~= dub_path~"ddox filter --min-protection=Protected docs.json";
  375. commands ~= dub_path~"ddox generate-html docs.json docs";
  376. version(Windows) commands ~= "xcopy /S /D "~dub_path~"public\\* docs\\";
  377. else commands ~= "cp -r "~dub_path~"public/* docs/";
  378. runCommands(commands);
  379. }
  380. }