1 /**
2 	A package manager.
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.dub;
9 
10 import dub.compilers.compiler;
11 import dub.dependency;
12 import dub.dependencyresolver;
13 import dub.internal.utils;
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.package_;
19 import dub.packagemanager;
20 import dub.packagesupplier;
21 import dub.project;
22 import dub.generators.generator;
23 import dub.init;
24 
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.process;
34 import std..string;
35 import std.typecons;
36 import std.zip;
37 import std.encoding : sanitize;
38 
39 // Workaround for libcurl liker errors when building with LDC
40 version (LDC) pragma(lib, "curl");
41 
42 
43 /// The default supplier for packages, which is the registry
44 /// hosted by code.dlang.org.
45 PackageSupplier[] defaultPackageSuppliers()
46 {
47 	URL url = URL.parse("http://code.dlang.org/");
48 	logDiagnostic("Using dub registry url '%s'", url);
49 	return [new RegistryPackageSupplier(url)];
50 }
51 
52 /// Option flags for fetch
53 enum FetchOptions
54 {
55 	none = 0,
56 	forceBranchUpgrade = 1<<0,
57 	usePrerelease = 1<<1,
58 	forceRemove = 1<<2,
59 	printOnly = 1<<3,
60 }
61 
62 /// The Dub class helps in getting the applications
63 /// dependencies up and running. An instance manages one application.
64 class Dub {
65 	private {
66 		bool m_dryRun = false;
67 		PackageManager m_packageManager;
68 		PackageSupplier[] m_packageSuppliers;
69 		Path m_rootPath;
70 		Path m_tempPath;
71 		Path m_userDubPath, m_systemDubPath;
72 		Json m_systemConfig, m_userConfig;
73 		Path m_projectPath;
74 		Project m_project;
75 		Path m_overrideSearchPath;
76 	}
77 
78 	/// Initiales the package manager for the vibe application
79 	/// under root.
80 	this(PackageSupplier[] additional_package_suppliers = null, string root_path = ".")
81 	{
82 		m_rootPath = Path(root_path);
83 		if (!m_rootPath.absolute) m_rootPath = Path(getcwd()) ~ m_rootPath;
84 
85 		version(Windows){
86 			m_systemDubPath = Path(environment.get("ProgramData")) ~ "dub/";
87 			m_userDubPath = Path(environment.get("APPDATA")) ~ "dub/";
88 			m_tempPath = Path(environment.get("TEMP"));
89 		} else version(Posix){
90 			m_systemDubPath = Path("/var/lib/dub/");
91 			m_userDubPath = Path(environment.get("HOME")) ~ ".dub/";
92 			if(!m_userDubPath.absolute)
93 				m_userDubPath = Path(getcwd()) ~ m_userDubPath;
94 			m_tempPath = Path("/tmp");
95 		}
96 
97 		m_userConfig = jsonFromFile(m_userDubPath ~ "settings.json", true);
98 		m_systemConfig = jsonFromFile(m_systemDubPath ~ "settings.json", true);
99 
100 		PackageSupplier[] ps = additional_package_suppliers;
101 		if (auto pp = "registryUrls" in m_userConfig)
102 			ps ~= deserializeJson!(string[])(*pp)
103 				.map!(url => cast(PackageSupplier)new RegistryPackageSupplier(URL(url)))
104 				.array;
105 		if (auto pp = "registryUrls" in m_systemConfig)
106 			ps ~= deserializeJson!(string[])(*pp)
107 				.map!(url => cast(PackageSupplier)new RegistryPackageSupplier(URL(url)))
108 				.array;
109 		ps ~= defaultPackageSuppliers();
110 
111 		auto cacheDir = m_userDubPath ~ "cache/";
112 		foreach (p; ps)
113 			p.cacheOp(cacheDir, CacheOp.load);
114 
115 		m_packageSuppliers = ps;
116 		m_packageManager = new PackageManager(m_userDubPath, m_systemDubPath);
117 		updatePackageSearchPath();
118 	}
119 
120 	/// Initializes DUB with only a single search path
121 	this(Path override_path)
122 	{
123 		m_overrideSearchPath = override_path;
124 		m_packageManager = new PackageManager(Path(), Path(), false);
125 		updatePackageSearchPath();
126 	}
127 
128 	/// Perform cleanup and persist caches to disk
129 	void shutdown()
130 	{
131 		auto cacheDir = m_userDubPath ~ "cache/";
132 		foreach (p; m_packageSuppliers)
133 			p.cacheOp(cacheDir, CacheOp.store);
134 	}
135 
136 	/// cleans all metadata caches
137 	void cleanCaches()
138 	{
139 		auto cacheDir = m_userDubPath ~ "cache/";
140 		foreach (p; m_packageSuppliers)
141 			p.cacheOp(cacheDir, CacheOp.clean);
142 	}
143 
144 	@property void dryRun(bool v) { m_dryRun = v; }
145 
146 	/** Returns the root path (usually the current working directory).
147 	*/
148 	@property Path rootPath() const { return m_rootPath; }
149 	/// ditto
150 	@property void rootPath(Path root_path)
151 	{
152 		m_rootPath = root_path;
153 		if (!m_rootPath.absolute) m_rootPath = Path(getcwd()) ~ m_rootPath;
154 	}
155 
156 	/// Returns the name listed in the dub.json of the current
157 	/// application.
158 	@property string projectName() const { return m_project.name; }
159 
160 	@property Path projectPath() const { return m_projectPath; }
161 
162 	@property string[] configurations() const { return m_project.configurations; }
163 
164 	@property inout(PackageManager) packageManager() inout { return m_packageManager; }
165 
166 	@property inout(Project) project() inout { return m_project; }
167 
168 	/// Loads the package from the current working directory as the main
169 	/// project package.
170 	void loadPackageFromCwd()
171 	{
172 		loadPackage(m_rootPath);
173 	}
174 
175 	/// Loads the package from the specified path as the main project package.
176 	void loadPackage(Path path)
177 	{
178 		m_projectPath = path;
179 		updatePackageSearchPath();
180 		m_project = new Project(m_packageManager, m_projectPath);
181 	}
182 
183 	/// Loads a specific package as the main project package (can be a sub package)
184 	void loadPackage(Package pack)
185 	{
186 		m_projectPath = pack.path;
187 		updatePackageSearchPath();
188 		m_project = new Project(m_packageManager, pack);
189 	}
190 
191 	void overrideSearchPath(Path path)
192 	{
193 		if (!path.absolute) path = Path(getcwd()) ~ path;
194 		m_overrideSearchPath = path;
195 		updatePackageSearchPath();
196 	}
197 
198 	string getDefaultConfiguration(BuildPlatform platform, bool allow_non_library_configs = true) const { return m_project.getDefaultConfiguration(platform, allow_non_library_configs); }
199 
200 	void upgrade(UpgradeOptions options)
201 	{
202 		// clear non-existent version selections
203 		if (!(options & UpgradeOptions.upgrade)) {
204 			next_pack:
205 			foreach (p; m_project.selections.selectedPackages) {
206 				auto dep = m_project.selections.getSelectedVersion(p);
207 				if (!dep.path.empty) {
208 					try if (m_packageManager.getOrLoadPackage(dep.path)) continue;
209 					catch (Exception e) { logDebug("Failed to load path based selection: %s", e.toString().sanitize); }
210 				} else {
211 					if (m_packageManager.getPackage(p, dep.version_)) continue;
212 					foreach (ps; m_packageSuppliers) {
213 						try {
214 							auto versions = ps.getVersions(p);
215 							if (versions.canFind!(v => dep.matches(v)))
216 								continue next_pack;
217 						} catch (Exception e) {
218 							logDiagnostic("Error querying versions for %s, %s: %s", p, ps.description, e.msg);
219 							logDebug("Full error: %s", e.toString().sanitize());
220 						}
221 					}
222 				}
223 
224 				logWarn("Selected package %s %s doesn't exist. Using latest matching version instead.", p, dep);
225 				m_project.selections.deselectVersion(p);
226 			}
227 		}
228 
229 		Dependency[string] versions;
230 		if ((options & UpgradeOptions.useCachedResult) && m_project.isUpgradeCacheUpToDate()) {
231 			logDiagnostic("Using cached upgrade results...");
232 			versions = m_project.getUpgradeCache();
233 		} else {
234 			auto resolver = new DependencyVersionResolver(this, options);
235 			versions = resolver.resolve(m_project.rootPackage, m_project.selections);
236 			if (options & UpgradeOptions.useCachedResult) {
237 				logDiagnostic("Caching upgrade results...");
238 				m_project.setUpgradeCache(versions);
239 			}
240 		}
241 
242 		if (options & UpgradeOptions.printUpgradesOnly) {
243 			bool any = false;
244 			string rootbasename = getBasePackageName(m_project.rootPackage.name);
245 
246 			foreach (p, ver; versions) {
247 				if (!ver.path.empty) continue;
248 
249 				auto basename = getBasePackageName(p);
250 				if (basename == rootbasename) continue;
251 
252 				if (!m_project.selections.hasSelectedVersion(basename)) {
253 					logInfo("Package %s can be installed with version %s.",
254 						basename, ver);
255 					any = true;
256 					continue;
257 				}
258 				auto sver = m_project.selections.getSelectedVersion(basename);
259 				if (!sver.path.empty) continue;
260 				if (ver.version_ <= sver.version_) continue;
261 				logInfo("Package %s can be upgraded from %s to %s.",
262 					basename, sver, ver);
263 				any = true;
264 			}
265 			if (any) logInfo("Use \"dub upgrade\" to perform those changes.");
266 			return;
267 		}
268 
269 		foreach (p, ver; versions) {
270 			assert(!p.canFind(":"), "Resolved packages contain a sub package!?: "~p);
271 			Package pack;
272 			if (!ver.path.empty) {
273 				try pack = m_packageManager.getOrLoadPackage(ver.path);
274 				catch (Exception e) {
275 					logDebug("Failed to load path based selection: %s", e.toString().sanitize);
276 					continue;
277 				}
278 			} else {
279 				pack = m_packageManager.getBestPackage(p, ver);
280 				if (pack && m_packageManager.isManagedPackage(pack)
281 					&& ver.version_.isBranch && (options & UpgradeOptions.upgrade) != 0)
282 				{
283 					// TODO: only re-install if there is actually a new commit available
284 					logInfo("Re-installing branch based dependency %s %s", p, ver.toString());
285 					m_packageManager.remove(pack, (options & UpgradeOptions.forceRemove) != 0);
286 					pack = null;
287 				}
288 			}
289 
290 			FetchOptions fetchOpts;
291 			fetchOpts |= (options & UpgradeOptions.preRelease) != 0 ? FetchOptions.usePrerelease : FetchOptions.none;
292 			fetchOpts |= (options & UpgradeOptions.forceRemove) != 0 ? FetchOptions.forceRemove : FetchOptions.none;
293 			if (!pack) fetch(p, ver, defaultPlacementLocation, fetchOpts, "getting selected version");
294 			if ((options & UpgradeOptions.select) && ver.path.empty && p != m_project.rootPackage.name)
295 				m_project.selections.selectVersion(p, ver.version_);
296 		}
297 
298 		m_project.reinit();
299 
300 		if (options & UpgradeOptions.select)
301 			m_project.saveSelections();
302 	}
303 
304 	/// Generate project files for a specified IDE.
305 	/// Any existing project files will be overridden.
306 	void generateProject(string ide, GeneratorSettings settings) {
307 		auto generator = createProjectGenerator(ide, m_project);
308 		if (m_dryRun) return; // TODO: pass m_dryRun to the generator
309 		generator.generate(settings);
310 	}
311 
312 	/// Executes tests on the current project. Throws an exception, if
313 	/// unittests failed.
314 	void testProject(GeneratorSettings settings, string config, Path custom_main_file)
315 	{
316 		if (custom_main_file.length && !custom_main_file.absolute) custom_main_file = getWorkingDirectory() ~ custom_main_file;
317 
318 		if (config.length == 0) {
319 			// if a custom main file was given, favor the first library configuration, so that it can be applied
320 			if (custom_main_file.length) config = m_project.getDefaultConfiguration(settings.platform, false);
321 			// else look for a "unittest" configuration
322 			if (!config.length && m_project.rootPackage.configurations.canFind("unittest")) config = "unittest";
323 			// if not found, fall back to the first "library" configuration
324 			if (!config.length) config = m_project.getDefaultConfiguration(settings.platform, false);
325 			// if still nothing found, use the first executable configuration
326 			if (!config.length) config = m_project.getDefaultConfiguration(settings.platform, true);
327 		}
328 
329 		auto generator = createProjectGenerator("build", m_project);
330 
331 		auto test_config = format("__test__%s__", config);
332 
333 		BuildSettings lbuildsettings = settings.buildSettings;
334 		m_project.addBuildSettings(lbuildsettings, settings.platform, config, null, true);
335 		if (lbuildsettings.targetType == TargetType.none) {
336 			logInfo(`Configuration '%s' has target type "none". Skipping test.`, config);
337 			return;
338 		}
339 
340 		if (lbuildsettings.targetType == TargetType.executable) {
341 			if (config == "unittest") logInfo("Running custom 'unittest' configuration.", config);
342 			else logInfo(`Configuration '%s' does not output a library. Falling back to "dub -b unittest -c %s".`, config, config);
343 			if (!custom_main_file.empty) logWarn("Ignoring custom main file.");
344 			settings.config = config;
345 		} else if (lbuildsettings.sourceFiles.empty) {
346 			logInfo(`No source files found in configuration '%s'. Falling back to "dub -b unittest".`, config);
347 			if (!custom_main_file.empty) logWarn("Ignoring custom main file.");
348 			settings.config = m_project.getDefaultConfiguration(settings.platform);
349 		} else {
350 			logInfo(`Generating test runner configuration '%s' for '%s' (%s).`, test_config, config, lbuildsettings.targetType);
351 
352 			BuildSettingsTemplate tcinfo = m_project.rootPackage.info.getConfiguration(config).buildSettings;
353 			tcinfo.targetType = TargetType.executable;
354 			tcinfo.targetName = test_config;
355 			tcinfo.versions[""] ~= "VibeCustomMain"; // HACK for vibe.d's legacy main() behavior
356 			string custommodname;
357 			if (custom_main_file.length) {
358 				import std.path;
359 				tcinfo.sourceFiles[""] ~= custom_main_file.relativeTo(m_project.rootPackage.path).toNativeString();
360 				tcinfo.importPaths[""] ~= custom_main_file.parentPath.toNativeString();
361 				custommodname = custom_main_file.head.toString().baseName(".d");
362 			}
363 
364 			string[] import_modules;
365 			foreach (file; lbuildsettings.sourceFiles) {
366 				if (file.endsWith(".d") && Path(file).head.toString() != "package.d")
367 					import_modules ~= lbuildsettings.determineModuleName(Path(file), m_project.rootPackage.path);
368 			}
369 
370 			// generate main file
371 			Path mainfile = getTempFile("dub_test_root", ".d");
372 			tcinfo.sourceFiles[""] ~= mainfile.toNativeString();
373 			tcinfo.mainSourceFile = mainfile.toNativeString();
374 			if (!m_dryRun) {
375 				auto fil = openFile(mainfile, FileMode.CreateTrunc);
376 				scope(exit) fil.close();
377 				fil.write("module dub_test_root;\n");
378 				fil.write("import std.typetuple;\n");
379 				foreach (mod; import_modules) fil.write(format("static import %s;\n", mod));
380 				fil.write("alias allModules = TypeTuple!(");
381 				foreach (i, mod; import_modules) {
382 					if (i > 0) fil.write(", ");
383 					fil.write(mod);
384 				}
385 				fil.write(");\n");
386 				if (custommodname.length) {
387 					fil.write(format("import %s;\n", custommodname));
388 				} else {
389 					fil.write(q{
390 						import std.stdio;
391 						import core.runtime;
392 
393 						void main() { writeln("All unit tests have been run successfully."); }
394 						shared static this() {
395 							version (Have_tested) {
396 								import tested;
397 								import core.runtime;
398 								import std.exception;
399 								Runtime.moduleUnitTester = () => true;
400 								//runUnitTests!app(new JsonTestResultWriter("results.json"));
401 								enforce(runUnitTests!allModules(new ConsoleTestResultWriter), "Unit tests failed.");
402 							}
403 						}
404 					});
405 				}
406 			}
407 			m_project.rootPackage.info.configurations ~= ConfigurationInfo(test_config, tcinfo);
408 			m_project = new Project(m_packageManager, m_project.rootPackage);
409 
410 			settings.config = test_config;
411 		}
412 
413 		generator.generate(settings);
414 	}
415 
416 	/// Outputs a JSON description of the project, including its dependencies.
417 	deprecated void describeProject(BuildPlatform platform, string config)
418 	{
419 		import std.stdio;
420 		auto desc = m_project.describe(platform, config);
421 		writeln(desc.serializeToPrettyJson());
422 	}
423 
424 	void listImportPaths(BuildPlatform platform, string config, string buildType, bool nullDelim)
425 	{
426 		import std.stdio;
427 
428 		foreach(path; m_project.listImportPaths(platform, config, buildType, nullDelim)) {
429 			writeln(path);
430 		}
431 	}
432 
433 	void listStringImportPaths(BuildPlatform platform, string config, string buildType, bool nullDelim)
434 	{
435 		import std.stdio;
436 
437 		foreach(path; m_project.listStringImportPaths(platform, config, buildType, nullDelim)) {
438 			writeln(path);
439 		}
440 	}
441 
442 	void listProjectData(BuildPlatform platform, string config, string buildType,
443 		string[] requestedData, Compiler formattingCompiler, bool nullDelim)
444 	{
445 		import std.stdio;
446 		import std.ascii : newline;
447 
448 		auto data = m_project.listBuildSettings(platform, config, buildType, requestedData, formattingCompiler, nullDelim);
449 		write( data.joiner(nullDelim? "\0" : newline) );
450 		if(!nullDelim)
451 			writeln();
452 	}
453 
454 	/// Cleans intermediate/cache files of the given package
455 	void cleanPackage(Path path)
456 	{
457 		logInfo("Cleaning package at %s...", path.toNativeString());
458 		enforce(!Package.findPackageFile(path).empty, "No package found.", path.toNativeString());
459 
460 		// TODO: clear target files and copy files
461 
462 		if (existsFile(path ~ ".dub/build")) rmdirRecurse((path ~ ".dub/build").toNativeString());
463 		if (existsFile(path ~ ".dub/obj")) rmdirRecurse((path ~ ".dub/obj").toNativeString());
464 	}
465 
466 
467 	/// Returns all cached packages as a "packageId" = "version" associative array
468 	string[string] cachedPackages() const { return m_project.cachedPackagesIDs; }
469 
470 	/// Fetches the package matching the dependency and places it in the specified location.
471 	Package fetch(string packageId, const Dependency dep, PlacementLocation location, FetchOptions options, string reason = "")
472 	{
473 		Json pinfo;
474 		PackageSupplier supplier;
475 		foreach(ps; m_packageSuppliers){
476 			try {
477 				pinfo = ps.getPackageDescription(packageId, dep, (options & FetchOptions.usePrerelease) != 0);
478 				supplier = ps;
479 				break;
480 			} catch(Exception e) {
481 				logDiagnostic("Package %s not found for %s: %s", packageId, ps.description, e.msg);
482 				logDebug("Full error: %s", e.toString().sanitize());
483 			}
484 		}
485 		enforce(pinfo.type != Json.Type.undefined, "No package "~packageId~" was found matching the dependency "~dep.toString());
486 		string ver = pinfo["version"].get!string;
487 
488 		Path placement;
489 		final switch (location) {
490 			case PlacementLocation.local: placement = m_rootPath; break;
491 			case PlacementLocation.user: placement = m_userDubPath ~ "packages/"; break;
492 			case PlacementLocation.system: placement = m_systemDubPath ~ "packages/"; break;
493 		}
494 
495 		// always upgrade branch based versions - TODO: actually check if there is a new commit available
496 		Package existing;
497 		try existing = m_packageManager.getPackage(packageId, ver, placement);
498 		catch (Exception e) {
499 			logWarn("Failed to load existing package %s: %s", ver, e.msg);
500 			logDiagnostic("Full error: %s", e.toString().sanitize);
501 		}
502 
503 		if (options & FetchOptions.printOnly) {
504 			if (existing && existing.vers != ver)
505 				logInfo("A new version for %s is available (%s -> %s). Run \"dub upgrade %s\" to switch.",
506 					packageId, existing.vers, ver, packageId);
507 			return null;
508 		}
509 
510 		if (existing) {
511 			if (!ver.startsWith("~") || !(options & FetchOptions.forceBranchUpgrade) || location == PlacementLocation.local) {
512 				// TODO: support git working trees by performing a "git pull" instead of this
513 				logDiagnostic("Package %s %s (%s) is already present with the latest version, skipping upgrade.",
514 					packageId, ver, placement);
515 				return existing;
516 			} else {
517 				logInfo("Removing %s %s to prepare replacement with a new version.", packageId, ver);
518 				if (!m_dryRun) m_packageManager.remove(existing, (options & FetchOptions.forceRemove) != 0);
519 			}
520 		}
521 
522 		if (reason.length) logInfo("Fetching %s %s (%s)...", packageId, ver, reason);
523 		else logInfo("Fetching %s %s...", packageId, ver);
524 		if (m_dryRun) return null;
525 
526 		logDiagnostic("Acquiring package zip file");
527 		auto dload = m_projectPath ~ ".dub/temp/downloads";
528 		auto tempfname = packageId ~ "-" ~ (ver.startsWith('~') ? ver[1 .. $] : ver) ~ ".zip";
529 		auto tempFile = m_tempPath ~ tempfname;
530 		string sTempFile = tempFile.toNativeString();
531 		if (exists(sTempFile)) std.file.remove(sTempFile);
532 		supplier.retrievePackage(tempFile, packageId, dep, (options & FetchOptions.usePrerelease) != 0); // Q: continue on fail?
533 		scope(exit) std.file.remove(sTempFile);
534 
535 		logInfo("Placing %s %s to %s...", packageId, ver, placement.toNativeString());
536 		auto clean_package_version = ver[ver.startsWith("~") ? 1 : 0 .. $];
537 		clean_package_version = clean_package_version.replace("+", "_"); // + has special meaning for Optlink
538 		Path dstpath = placement ~ (packageId ~ "-" ~ clean_package_version);
539 
540 		return m_packageManager.storeFetchedPackage(tempFile, pinfo, dstpath);
541 	}
542 
543 	/// Removes a given package from the list of present/cached modules.
544 	/// @removeFromApplication: if true, this will also remove an entry in the
545 	/// list of dependencies in the application's dub.json
546 	void remove(in Package pack, bool force_remove)
547 	{
548 		logInfo("Removing %s in %s", pack.name, pack.path.toNativeString());
549 		if (!m_dryRun) m_packageManager.remove(pack, force_remove);
550 	}
551 
552 	/// @see remove(string, string, RemoveLocation)
553 	enum RemoveVersionWildcard = "*";
554 
555 	/// This will remove a given package with a specified version from the
556 	/// location.
557 	/// It will remove at most one package, unless @param version_ is
558 	/// specified as wildcard "*".
559 	/// @param package_id Package to be removed
560 	/// @param version_ Identifying a version or a wild card. An empty string
561 	/// may be passed into. In this case the package will be removed from the
562 	/// location, if there is only one version retrieved. This will throw an
563 	/// exception, if there are multiple versions retrieved.
564 	/// Note: as wildcard string only RemoveVersionWildcard ("*") is supported.
565 	/// @param location_
566 	void remove(string package_id, string version_, PlacementLocation location_, bool force_remove)
567 	{
568 		enforce(!package_id.empty);
569 		if (location_ == PlacementLocation.local) {
570 			logInfo("To remove a locally placed package, make sure you don't have any data"
571 					~ "\nleft in it's directory and then simply remove the whole directory.");
572 			throw new Exception("dub cannot remove locally installed packages.");
573 		}
574 
575 		Package[] packages;
576 		const bool wildcardOrEmpty = version_ == RemoveVersionWildcard || version_.empty;
577 
578 		// Retrieve packages to be removed.
579 		foreach(pack; m_packageManager.getPackageIterator(package_id))
580 			if( wildcardOrEmpty || pack.vers == version_ )
581 				packages ~= pack;
582 
583 		// Check validity of packages to be removed.
584 		if(packages.empty) {
585 			throw new Exception("Cannot find package to remove. ("
586 				~ "id: '" ~ package_id ~ "', version: '" ~ version_ ~ "', location: '" ~ to!string(location_) ~ "'"
587 				~ ")");
588 		}
589 		if(version_.empty && packages.length > 1) {
590 			logError("Cannot remove package '" ~ package_id ~ "', there are multiple possibilities at location\n"
591 				~ "'" ~ to!string(location_) ~ "'.");
592 			logError("Available versions:");
593 			foreach(pack; packages)
594 				logError("  %s", pack.vers);
595 			throw new Exception("Please specify a individual version using --version=... or use the"
596 				~ " wildcard --version=" ~ RemoveVersionWildcard ~ " to remove all versions.");
597 		}
598 
599 		logDebug("Removing %s packages.", packages.length);
600 		foreach(pack; packages) {
601 			try {
602 				remove(pack, force_remove);
603 				logInfo("Removed %s, version %s.", package_id, pack.vers);
604 			} catch (Exception e) {
605 				logError("Failed to remove %s %s: %s", package_id, pack.vers, e.msg);
606 				logInfo("Continuing with other packages (if any).");
607 			}
608 		}
609 	}
610 
611 	void addLocalPackage(string path, string ver, bool system)
612 	{
613 		if (m_dryRun) return;
614 		m_packageManager.addLocalPackage(makeAbsolute(path), ver, system ? LocalPackageType.system : LocalPackageType.user);
615 	}
616 
617 	void removeLocalPackage(string path, bool system)
618 	{
619 		if (m_dryRun) return;
620 		m_packageManager.removeLocalPackage(makeAbsolute(path), system ? LocalPackageType.system : LocalPackageType.user);
621 	}
622 
623 	void addSearchPath(string path, bool system)
624 	{
625 		if (m_dryRun) return;
626 		m_packageManager.addSearchPath(makeAbsolute(path), system ? LocalPackageType.system : LocalPackageType.user);
627 	}
628 
629 	void removeSearchPath(string path, bool system)
630 	{
631 		if (m_dryRun) return;
632 		m_packageManager.removeSearchPath(makeAbsolute(path), system ? LocalPackageType.system : LocalPackageType.user);
633 	}
634 
635 	void createEmptyPackage(Path path, string[] deps, string type)
636 	{
637 		if (!path.absolute) path = m_rootPath ~ path;
638 		path.normalize();
639 
640 		if (m_dryRun) return;
641 		string[string] depVers;
642 		string[] notFound; // keep track of any failed packages in here
643 		foreach(ps; this.m_packageSuppliers){
644 			foreach(dep; deps){
645 				try{
646 					auto versionStrings = ps.getVersions(dep);
647 					depVers[dep] = versionStrings[$-1].toString;
648 				} catch(Exception e){
649 					notFound ~= dep;
650 				}
651 			}
652 		}
653 		if(notFound.length > 1){
654 			throw new Exception(format("Couldn't find packages: %-(%s, %).", notFound));
655 		}
656 		else if(notFound.length == 1){
657 			throw new Exception(format("Couldn't find package: %-(%s, %).", notFound));
658 		}
659 
660 		initPackage(path, depVers, type);
661 
662 		//Act smug to the user.
663 		logInfo("Successfully created an empty project in '%s'.", path.toNativeString());
664 	}
665 
666 	void runDdox(bool run)
667 	{
668 		if (m_dryRun) return;
669 
670 		auto ddox_pack = m_packageManager.getBestPackage("ddox", ">=0.0.0");
671 		if (!ddox_pack) ddox_pack = m_packageManager.getBestPackage("ddox", "~master");
672 		if (!ddox_pack) {
673 			logInfo("DDOX is not present, getting it and storing user wide");
674 			ddox_pack = fetch("ddox", Dependency(">=0.0.0"), defaultPlacementLocation, FetchOptions.none);
675 		}
676 
677 		version(Windows) auto ddox_exe = "ddox.exe";
678 		else auto ddox_exe = "ddox";
679 
680 		if( !existsFile(ddox_pack.path~ddox_exe) ){
681 			logInfo("DDOX in %s is not built, performing build now.", ddox_pack.path.toNativeString());
682 
683 			auto ddox_dub = new Dub(m_packageSuppliers);
684 			ddox_dub.loadPackage(ddox_pack.path);
685 			ddox_dub.upgrade(UpgradeOptions.select);
686 
687 			auto compiler_binary = "dmd";
688 
689 			GeneratorSettings settings;
690 			settings.config = "application";
691 			settings.compiler = getCompiler(compiler_binary);
692 			settings.platform = settings.compiler.determinePlatform(settings.buildSettings, compiler_binary);
693 			settings.buildType = "debug";
694 			ddox_dub.generateProject("build", settings);
695 
696 			//runCommands(["cd "~ddox_pack.path.toNativeString()~" && dub build -v"]);
697 		}
698 
699 		auto p = ddox_pack.path;
700 		p.endsWithSlash = true;
701 		auto dub_path = p.toNativeString();
702 
703 		string[] commands;
704 		string[] filterargs = m_project.rootPackage.info.ddoxFilterArgs.dup;
705 		if (filterargs.empty) filterargs = ["--min-protection=Protected", "--only-documented"];
706 		commands ~= dub_path~"ddox filter "~filterargs.join(" ")~" docs.json";
707 		if (!run) {
708 			commands ~= dub_path~"ddox generate-html --navigation-type=ModuleTree docs.json docs";
709 			version(Windows) commands ~= "xcopy /S /D "~dub_path~"public\\* docs\\";
710 			else commands ~= "rsync -ru '"~dub_path~"public/' docs/";
711 		}
712 		runCommands(commands);
713 
714 		if (run) {
715 			auto proc = spawnProcess([dub_path~"ddox", "serve-html", "--navigation-type=ModuleTree", "docs.json", "--web-file-dir="~dub_path~"public"]);
716 			browse("http://127.0.0.1:8080/");
717 			wait(proc);
718 		}
719 	}
720 
721 	private void updatePackageSearchPath()
722 	{
723 		if (m_overrideSearchPath.length) {
724 			m_packageManager.disableDefaultSearchPaths = true;
725 			m_packageManager.searchPath = [m_overrideSearchPath];
726 		} else {
727 			auto p = environment.get("DUBPATH");
728 			Path[] paths;
729 
730 			version(Windows) enum pathsep = ";";
731 			else enum pathsep = ":";
732 			if (p.length) paths ~= p.split(pathsep).map!(p => Path(p))().array();
733 			m_packageManager.disableDefaultSearchPaths = false;
734 			m_packageManager.searchPath = paths;
735 		}
736 	}
737 
738 	private Path makeAbsolute(Path p) const { return p.absolute ? p : m_rootPath ~ p; }
739 	private Path makeAbsolute(string p) const { return makeAbsolute(Path(p)); }
740 }
741 
742 string determineModuleName(BuildSettings settings, Path file, Path base_path)
743 {
744 	assert(base_path.absolute);
745 	if (!file.absolute) file = base_path ~ file;
746 
747 	size_t path_skip = 0;
748 	foreach (ipath; settings.importPaths.map!(p => Path(p))) {
749 		if (!ipath.absolute) ipath = base_path ~ ipath;
750 		assert(!ipath.empty);
751 		if (file.startsWith(ipath) && ipath.length > path_skip)
752 			path_skip = ipath.length;
753 	}
754 
755 	enforce(path_skip > 0,
756 		format("Source file '%s' not found in any import path.", file.toNativeString()));
757 
758 	auto mpath = file[path_skip .. file.length];
759 	auto ret = appender!string;
760 
761 	//search for module keyword in file
762 	string moduleName = getModuleNameFromFile(file.to!string);
763 
764 	if(moduleName.length) return moduleName;
765 
766 	//create module name from path
767 	foreach (i; 0 .. mpath.length) {
768 		import std.path;
769 		auto p = mpath[i].toString();
770 		if (p == "package.d") break;
771 		if (i > 0) ret ~= ".";
772 		if (i+1 < mpath.length) ret ~= p;
773 		else ret ~= p.baseName(".d");
774 	}
775 
776 	return ret.data;
777 }
778 
779 /**
780  * Search for module keyword in D Code
781  */
782 string getModuleNameFromContent(string content) {
783 	import std.regex;
784 	import std..string;
785 
786 	content = content.strip;
787 	if (!content.length) return null;
788 
789 	static bool regex_initialized = false;
790 	static Regex!char comments_pattern, module_pattern;
791 
792 	if (!regex_initialized) {
793 		comments_pattern = regex(`(/\*([^*]|[\r\n]|(\*+([^*/]|[\r\n])))*\*+/)|(//.*)`, "g");
794 		module_pattern = regex(`module\s+([\w\.]+)\s*;`, "g");
795 		regex_initialized = true;
796 	}
797 
798 	content = replaceAll(content, comments_pattern, "");
799 	auto result = matchFirst(content, module_pattern);
800 
801 	string moduleName;
802 	if(!result.empty) moduleName = result.front;
803 
804 	if (moduleName.length >= 7) moduleName = moduleName[7..$-1];
805 
806 	return moduleName;
807 }
808 
809 unittest {
810 	//test empty string
811 	string name = getModuleNameFromContent("");
812 	assert(name == "", "can't get module name from empty string");
813 
814 	//test simple name
815 	name = getModuleNameFromContent("module myPackage.myModule;");
816 	assert(name == "myPackage.myModule", "can't parse module name");
817 
818 	//test if it can ignore module inside comments
819 	name = getModuleNameFromContent("/**
820 	module fakePackage.fakeModule;
821 	*/
822 	module myPackage.myModule;");
823 
824 	assert(name == "myPackage.myModule", "can't parse module name");
825 
826 	name = getModuleNameFromContent("//module fakePackage.fakeModule;
827 	module myPackage.myModule;");
828 
829 	assert(name == "myPackage.myModule", "can't parse module name");
830 }
831 
832 /**
833  * Search for module keyword in file
834  */
835 string getModuleNameFromFile(string filePath) {
836 	string fileContent = filePath.readText;
837 
838 	logDiagnostic("Get module name from path: " ~ filePath);
839 	return getModuleNameFromContent(fileContent);
840 }
841 
842 enum UpgradeOptions
843 {
844 	none = 0,
845 	upgrade = 1<<1, /// Upgrade existing packages
846 	preRelease = 1<<2, /// inclde pre-release versions in upgrade
847 	forceRemove = 1<<3, /// Force removing package folders, which contain unknown files
848 	select = 1<<4, /// Update the dub.selections.json file with the upgraded versions
849 	printUpgradesOnly = 1<<5, /// Instead of downloading new packages, just print a message to notify the user of their existence
850 	useCachedResult = 1<<6, /// Use cached information stored with the package to determine upgrades
851 }
852 
853 class DependencyVersionResolver : DependencyResolver!(Dependency, Dependency) {
854 	protected {
855 		Dub m_dub;
856 		UpgradeOptions m_options;
857 		Dependency[][string] m_packageVersions;
858 		Package[string] m_remotePackages;
859 		SelectedVersions m_selectedVersions;
860 		Package m_rootPackage;
861 	}
862 
863 
864 	this(Dub dub, UpgradeOptions options)
865 	{
866 		m_dub = dub;
867 		m_options = options;
868 	}
869 
870 	Dependency[string] resolve(Package root, SelectedVersions selected_versions)
871 	{
872 		m_rootPackage = root;
873 		m_selectedVersions = selected_versions;
874 		return super.resolve(TreeNode(root.name, Dependency(root.ver)), (m_options & UpgradeOptions.printUpgradesOnly) == 0);
875 	}
876 
877 	protected override Dependency[] getAllConfigs(string pack)
878 	{
879 		if (auto pvers = pack in m_packageVersions)
880 			return *pvers;
881 
882 		if (!(m_options & UpgradeOptions.upgrade) && m_selectedVersions.hasSelectedVersion(pack)) {
883 			auto ret = [m_selectedVersions.getSelectedVersion(pack)];
884 			logDiagnostic("Using fixed selection %s %s", pack, ret[0]);
885 			m_packageVersions[pack] = ret;
886 			return ret;
887 		}
888 
889 		logDiagnostic("Search for versions of %s (%s package suppliers)", pack, m_dub.m_packageSuppliers.length);
890 		Version[] versions;
891 		foreach (p; m_dub.packageManager.getPackageIterator(pack))
892 			versions ~= p.ver;
893 
894 		foreach (ps; m_dub.m_packageSuppliers) {
895 			try {
896 				auto vers = ps.getVersions(pack);
897 				vers.reverse();
898 				if (!vers.length) {
899 					logDiagnostic("No versions for %s for %s", pack, ps.description);
900 					continue;
901 				}
902 
903 				versions ~= vers;
904 				break;
905 			} catch (Exception e) {
906 				logDebug("Package %s not found in %s: %s", pack, ps.description, e.msg);
907 				logDebug("Full error: %s", e.toString().sanitize);
908 			}
909 		}
910 
911 		// sort by version, descending, and remove duplicates
912 		versions = versions.sort!"a>b".uniq.array;
913 
914 		// move pre-release versions to the back of the list if no preRelease flag is given
915 		if (!(m_options & UpgradeOptions.preRelease))
916 			versions = versions.filter!(v => !v.isPreRelease).array ~ versions.filter!(v => v.isPreRelease).array;
917 
918 		if (!versions.length) logDiagnostic("Nothing found for %s", pack);
919 
920 		auto ret = versions.map!(v => Dependency(v)).array;
921 		m_packageVersions[pack] = ret;
922 		return ret;
923 	}
924 
925 	protected override Dependency[] getSpecificConfigs(TreeNodes nodes)
926 	{
927 		if (!nodes.configs.path.empty) return [nodes.configs];
928 		else return null;
929 	}
930 
931 
932 	protected override TreeNodes[] getChildren(TreeNode node)
933 	{
934 		auto ret = appender!(TreeNodes[]);
935 		auto pack = getPackage(node.pack, node.config);
936 		if (!pack) {
937 			// this can hapen when the package description contains syntax errors
938 			logDebug("Invalid package in dependency tree: %s %s", node.pack, node.config);
939 			return null;
940 		}
941 		auto basepack = pack.basePackage;
942 
943 		foreach (dname, dspec; pack.dependencies) {
944 			auto dbasename = getBasePackageName(dname);
945 
946 			// detect dependencies to the root package (or sub packages thereof)
947 			if (dbasename == basepack.name) {
948 				auto absdeppath = dspec.mapToPath(pack.path).path;
949 				auto subpack = m_dub.m_packageManager.getSubPackage(basepack, getSubPackageName(dname), true);
950 				if (subpack) {
951 					auto desireddeppath = dname == dbasename ? basepack.path : subpack.path;
952 					enforce(dspec.path.empty || absdeppath == desireddeppath,
953 						format("Dependency from %s to root package references wrong path: %s vs. %s",
954 							node.pack, absdeppath.toNativeString(), desireddeppath.toNativeString()));
955 				}
956 				ret ~= TreeNodes(dname, node.config);
957 				continue;
958 			}
959 
960 			if (dspec.optional && !m_dub.packageManager.getFirstPackage(dname))
961 				continue;
962 			if (m_options & UpgradeOptions.upgrade || !m_selectedVersions || !m_selectedVersions.hasSelectedVersion(dbasename))
963 				ret ~= TreeNodes(dname, dspec.mapToPath(pack.path));
964 			else ret ~= TreeNodes(dname, m_selectedVersions.getSelectedVersion(dbasename));
965 		}
966 		return ret.data;
967 	}
968 
969 	protected override bool matches(Dependency configs, Dependency config)
970 	{
971 		if (!configs.path.empty) return configs.path == config.path;
972 		return configs.merge(config).valid;
973 	}
974 
975 	private Package getPackage(string name, Dependency dep)
976 	{
977 		auto basename = getBasePackageName(name);
978 
979 		// for sub packages, first try to get them from the base package
980 		if (basename != name) {
981 			auto subname = getSubPackageName(name);
982 			auto basepack = getPackage(basename, dep);
983 			if (!basepack) return null;
984 			if (auto sp = m_dub.m_packageManager.getSubPackage(basepack, subname, true)) {
985 				return sp;
986 			} else if (!basepack.subPackages.canFind!(p => p.path.length)) {
987 				// note: external sub packages are handled further below
988 				auto spr = basepack.getInternalSubPackage(subname);
989 				if (!spr.isNull) {
990 					auto sp = new Package(spr, basepack.path, basepack);
991 					m_remotePackages[sp.name] = sp;
992 					return sp;
993 				} else {
994 					logDiagnostic("Sub package %s doesn't exist in %s %s.", name, basename, dep.version_);
995 					return null;
996 				}
997 			} else if (auto ret = m_dub.m_packageManager.getBestPackage(name, dep)) {
998 				return ret;
999 			} else {
1000 				logDiagnostic("External sub package %s %s not found.", name, dep.version_);
1001 				return null;
1002 			}
1003 		}
1004 
1005 		if (!dep.path.empty) {
1006 			try {
1007 				auto ret = m_dub.packageManager.getOrLoadPackage(dep.path);
1008 				if (dep.matches(ret.ver)) return ret;
1009 			} catch (Exception e) {
1010 				logDiagnostic("Failed to load path based dependency %s: %s", name, e.msg);
1011 				logDebug("Full error: %s", e.toString().sanitize);
1012 				return null;
1013 			}
1014 		}
1015 
1016 		if (auto ret = m_dub.m_packageManager.getBestPackage(name, dep))
1017 			return ret;
1018 
1019 		auto key = name ~ ":" ~ dep.version_.toString();
1020 		if (auto ret = key in m_remotePackages)
1021 			return *ret;
1022 
1023 		auto prerelease = (m_options & UpgradeOptions.preRelease) != 0;
1024 
1025 		auto rootpack = name.split(":")[0];
1026 
1027 		foreach (ps; m_dub.m_packageSuppliers) {
1028 			if (rootpack == name) {
1029 				try {
1030 					auto desc = ps.getPackageDescription(name, dep, prerelease);
1031 					auto ret = new Package(desc);
1032 					m_remotePackages[key] = ret;
1033 					return ret;
1034 				} catch (Exception e) {
1035 					logDiagnostic("Metadata for %s %s could not be downloaded from %s: %s", name, dep, ps.description, e.msg);
1036 					logDebug("Full error: %s", e.toString().sanitize);
1037 				}
1038 			} else {
1039 				logDiagnostic("Package %s not found in base package description (%s). Downloading whole package.", name, dep.version_.toString());
1040 				try {
1041 					FetchOptions fetchOpts;
1042 					fetchOpts |= prerelease ? FetchOptions.usePrerelease : FetchOptions.none;
1043 					fetchOpts |= (m_options & UpgradeOptions.forceRemove) != 0 ? FetchOptions.forceRemove : FetchOptions.none;
1044 					m_dub.fetch(rootpack, dep, defaultPlacementLocation, fetchOpts, "need sub package description");
1045 					auto ret = m_dub.m_packageManager.getBestPackage(name, dep);
1046 					if (!ret) {
1047 						logWarn("Package %s %s doesn't have a sub package %s", rootpack, dep.version_, name);
1048 						return null;
1049 					}
1050 					m_remotePackages[key] = ret;
1051 					return ret;
1052 				} catch (Exception e) {
1053 					logDiagnostic("Package %s could not be downloaded from %s: %s", rootpack, ps.description, e.msg);
1054 					logDebug("Full error: %s", e.toString().sanitize);
1055 				}
1056 			}
1057 		}
1058 
1059 		m_remotePackages[key] = null;
1060 
1061 		logWarn("Package %s %s could not be loaded either locally, or from the configured package registries.", name, dep);
1062 		return null;
1063 	}
1064 }
1065