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