1 module workspaced.com.dub;
2 
3 import core.exception;
4 import core.sync.mutex;
5 import core.thread;
6 
7 import std.algorithm;
8 import std.array : appender;
9 import std.conv;
10 import std.exception;
11 import std.json : JSONType, JSONValue;
12 import std.parallelism;
13 import std.regex;
14 import std.stdio;
15 import std.string;
16 
17 import painlessjson : toJSON, fromJSON;
18 
19 import workspaced.api;
20 
21 import dub.description;
22 import dub.dub;
23 import dub.package_;
24 import dub.project;
25 
26 import dub.compilers.buildsettings;
27 import dub.compilers.compiler;
28 import dub.dependency;
29 import dub.generators.build;
30 import dub.generators.generator;
31 
32 import dub.internal.vibecompat.core.log;
33 import dub.internal.vibecompat.inet.url;
34 
35 import dub.recipe.io;
36 
37 @component("dub")
38 class DubComponent : ComponentWrapper
39 {
40 	mixin DefaultComponentWrapper;
41 
42 	static void registered()
43 	{
44 		setLogLevel(LogLevel.none);
45 	}
46 
47 	protected void load()
48 	{
49 		if (!refInstance)
50 			throw new Exception("dub requires to be instanced");
51 
52 		if (config.get!bool("dub", "registerImportProvider", true))
53 			importPathProvider = &imports;
54 		if (config.get!bool("dub", "registerStringImportProvider", true))
55 			stringImportPathProvider = &stringImports;
56 		if (config.get!bool("dub", "registerImportFilesProvider", false))
57 			importFilesProvider = &fileImports;
58 		if (config.get!bool("dub", "registerProjectVersionsProvider", true))
59 			projectVersionsProvider = &versions;
60 		if (config.get!bool("dub", "registerDebugSpecificationsProvider", true))
61 			debugSpecificationsProvider = &debugVersions;
62 
63 		try
64 		{
65 			start();
66 
67 			_configuration = _dub.project.getDefaultConfiguration(_platform);
68 			if (!_dub.project.configurations.canFind(_configuration))
69 			{
70 				stderr.writeln("Dub Error: No configuration available");
71 				workspaced.broadcast(refInstance, JSONValue([
72 							"type": JSONValue("warning"),
73 							"component": JSONValue("dub"),
74 							"detail": JSONValue("invalid-default-config")
75 						]));
76 			}
77 			else
78 				updateImportPaths(false);
79 		}
80 		catch (Exception e)
81 		{
82 			if (!_dub || !_dub.project)
83 				throw e;
84 			stderr.writeln("Dub Error (ignored): ", e);
85 		}
86 		/*catch (AssertError e)
87 		{
88 			if (!_dub || !_dub.project)
89 				throw e;
90 			stderr.writeln("Dub Error (ignored): ", e);
91 		}*/
92 	}
93 
94 	private void start()
95 	{
96 		_dubRunning = false;
97 		_dub = new Dub(instance.cwd, null, SkipPackageSuppliers.none);
98 		_dub.packageManager.getOrLoadPackage(NativePath(instance.cwd));
99 		_dub.loadPackage();
100 		_dub.project.validate();
101 
102 		// mark all packages as optional so we don't crash
103 		int missingPackages;
104 		auto optionalified = optionalifyPackages;
105 		foreach (ref pkg; _dub.project.getTopologicalPackageList())
106 		{
107 			optionalifyRecipe(pkg);
108 			foreach (dep; pkg.getAllDependencies()
109 				.filter!(a => optionalified.canFind(a.name)))
110 			{
111 				auto d = _dub.project.getDependency(dep.name, true);
112 				if (!d)
113 					missingPackages++;
114 				else
115 					optionalifyRecipe(d);
116 			}
117 		}
118 
119 		if (!_compilerBinaryName.length)
120 			_compilerBinaryName = _dub.defaultCompiler;
121 		setCompiler(_compilerBinaryName);
122 
123 		_settingsTemplate = cast() _dub.project.rootPackage.getBuildSettings();
124 
125 		if (missingPackages > 0)
126 		{
127 			upgrade(false);
128 			optionalifyPackages();
129 		}
130 
131 		_dubRunning = true;
132 	}
133 
134 	private string[] optionalifyPackages()
135 	{
136 		bool[Package] visited;
137 		string[] optionalified;
138 		foreach (pkg; _dub.project.dependencies)
139 			optionalified ~= optionalifyRecipe(cast() pkg);
140 		return optionalified;
141 	}
142 
143 	private string[] optionalifyRecipe(Package pkg)
144 	{
145 		string[] optionalified;
146 		foreach (key, ref value; pkg.recipe.buildSettings.dependencies)
147 		{
148 			if (!value.optional)
149 			{
150 				value.optional = true;
151 				value.default_ = true;
152 				optionalified ~= key;
153 			}
154 		}
155 		foreach (ref config; pkg.recipe.configurations)
156 			foreach (key, ref value; config.buildSettings.dependencies)
157 			{
158 				if (!value.optional)
159 				{
160 					value.optional = true;
161 					value.default_ = true;
162 					optionalified ~= key;
163 				}
164 			}
165 		return optionalified;
166 	}
167 
168 	private void restart()
169 	{
170 		_dub.destroy();
171 		_dubRunning = false;
172 		start();
173 	}
174 
175 	bool isRunning()
176 	{
177 		return _dub !is null
178 			&& _dub.project !is null
179 			&& _dub.project.rootPackage !is null
180 			&& _dubRunning;
181 	}
182 
183 	/// Reloads the dub.json or dub.sdl file from the cwd
184 	/// Returns: `false` if there are no import paths available
185 	Future!bool update()
186 	{
187 		restart();
188 		mixin(gthreadsAsyncProxy!`updateImportPaths(false)`);
189 	}
190 
191 	bool updateImportPaths(bool restartDub = true)
192 	{
193 		validateConfiguration();
194 
195 		if (restartDub)
196 			restart();
197 
198 		GeneratorSettings settings;
199 		settings.platform = _platform;
200 		settings.config = _configuration;
201 		settings.buildType = _buildType;
202 		settings.compiler = _compiler;
203 		settings.buildSettings = _settings;
204 		settings.buildSettings.addOptions(BuildOption.syntaxOnly);
205 		settings.combined = true;
206 		settings.run = false;
207 
208 		try
209 		{
210 			auto paths = _dub.project.listBuildSettings(settings, [
211 					"import-paths", "string-import-paths", "source-files", "versions", "debug-versions"
212 					], ListBuildSettingsFormat.listNul);
213 			_importPaths = paths[0].split('\0');
214 			_stringImportPaths = paths[1].split('\0');
215 			_importFiles = paths[2].split('\0');
216 			_versions = paths[3].split('\0');
217 			_debugVersions = paths[4].split('\0');
218 			return _importPaths.length > 0 || _importFiles.length > 0;
219 		}
220 		catch (Exception e)
221 		{
222 			workspaced.broadcast(refInstance, JSONValue([
223 						"type": JSONValue("error"),
224 						"component": JSONValue("dub"),
225 						"detail": JSONValue("Error while listing import paths: " ~ e.toString)
226 					]));
227 			_importPaths = [];
228 			_stringImportPaths = [];
229 			return false;
230 		}
231 	}
232 
233 	/// Calls `dub upgrade`
234 	void upgrade(bool save = true)
235 	{
236 		if (save)
237 			_dub.upgrade(UpgradeOptions.select | UpgradeOptions.upgrade);
238 		else
239 			_dub.upgrade(UpgradeOptions.noSaveSelections);
240 	}
241 
242 	/// Throws if configuration is invalid, otherwise does nothing.
243 	void validateConfiguration() const
244 	{
245 		if (!_dub.project.configurations.canFind(_configuration))
246 			throw new Exception("Cannot use dub with invalid configuration");
247 	}
248 
249 	/// Throws if configuration is invalid or targetType is none or source library, otherwise does nothing.
250 	void validateBuildConfiguration()
251 	{
252 		if (!_dub.project.configurations.canFind(_configuration))
253 			throw new Exception("Cannot use dub with invalid configuration");
254 		if (_settings.targetType == TargetType.none)
255 			throw new Exception("Cannot build with dub with targetType == none");
256 		if (_settings.targetType == TargetType.sourceLibrary)
257 			throw new Exception("Cannot build with dub with targetType == sourceLibrary");
258 	}
259 
260 	/// Lists all dependencies. This will go through all dependencies and contain the dependencies of dependencies. You need to create a tree structure from this yourself.
261 	/// Returns: `[{dependencies: string[string], ver: string, name: string}]`
262 	auto dependencies() @property const
263 	{
264 		validateConfiguration();
265 
266 		return listDependencies(_dub.project);
267 	}
268 
269 	/// Lists dependencies of the root package. This can be used as a base to create a tree structure.
270 	string[] rootDependencies() @property const
271 	{
272 		validateConfiguration();
273 
274 		return listDependencies(_dub.project.rootPackage);
275 	}
276 
277 	/// Returns the path to the root package recipe (dub.json/dub.sdl)
278 	///
279 	/// Note that this can be empty if the package is not in the local file system.
280 	string recipePath() @property
281 	{
282 		return _dub.project.rootPackage.recipePath.toString;
283 	}
284 
285 	/// Re-parses the package recipe on the file system and returns if the syntax is valid.
286 	/// Returns: empty string/null if no error occured, error message if an error occured.
287 	string validateRecipeSyntaxOnFileSystem()
288 	{
289 		auto p = recipePath;
290 		if (!p.length)
291 			return "Package is not in local file system";
292 
293 		try
294 		{
295 			readPackageRecipe(p);
296 			return null;
297 		}
298 		catch (Exception e)
299 		{
300 			return e.msg;
301 		}
302 	}
303 
304 	/// Lists all import paths
305 	string[] imports() @property nothrow
306 	{
307 		return _importPaths;
308 	}
309 
310 	/// Lists all string import paths
311 	string[] stringImports() @property nothrow
312 	{
313 		return _stringImportPaths;
314 	}
315 
316 	/// Lists all import paths to files
317 	string[] fileImports() @property nothrow
318 	{
319 		return _importFiles;
320 	}
321 
322 	/// Lists the currently defined versions
323 	string[] versions() @property nothrow
324 	{
325 		return _versions;
326 	}
327 
328 	/// Lists the currently defined debug versions (debug specifications)
329 	string[] debugVersions() @property nothrow
330 	{
331 		return _debugVersions;
332 	}
333 
334 	/// Lists all configurations defined in the package description
335 	string[] configurations() @property
336 	{
337 		return _dub.project.configurations;
338 	}
339 
340 	PackageBuildSettings rootPackageBuildSettings() @property
341 	{
342 		auto pkg = _dub.project.rootPackage;
343 		BuildSettings settings = pkg.getBuildSettings(_platform, _configuration);
344 		return PackageBuildSettings(settings,
345 				pkg.path.toString,
346 				pkg.name,
347 				_dub.project.rootPackage.recipePath.toNativeString());
348 	}
349 
350 	/// Lists all build types defined in the package description AND the predefined ones from dub ("plain", "debug", "release", "release-debug", "release-nobounds", "unittest", "docs", "ddox", "profile", "profile-gc", "cov", "unittest-cov")
351 	string[] buildTypes() const @property
352 	{
353 		string[] types = [
354 			"plain", "debug", "release", "release-debug", "release-nobounds",
355 			"unittest", "docs", "ddox", "profile", "profile-gc", "cov", "unittest-cov"
356 		];
357 		foreach (type, info; _dub.project.rootPackage.recipe.buildTypes)
358 			types ~= type;
359 		return types;
360 	}
361 
362 	/// Gets the current selected configuration
363 	string configuration() const @property
364 	{
365 		return _configuration;
366 	}
367 
368 	/// Selects a new configuration and updates the import paths accordingly
369 	/// Returns: `false` if there are no import paths in the new configuration
370 	bool setConfiguration(string configuration)
371 	{
372 		if (!_dub.project.configurations.canFind(configuration))
373 			return false;
374 		_configuration = configuration;
375 		_settingsTemplate = cast() _dub.project.rootPackage.getBuildSettings(configuration);
376 		return updateImportPaths(false);
377 	}
378 
379 	/// List all possible arch types for current set compiler
380 	string[] archTypes() const @property
381 	{
382 		auto types = appender!(string[]);
383 		types ~= ["x86_64", "x86"];
384 
385 		string compilerName = _compiler.name;
386 
387 		if (compilerName == "dmd")
388 		{
389 			// https://github.com/dlang/dub/blob/master/source/dub/compilers/dmd.d#L110
390 			version (Windows)
391 			{
392 				types ~= ["x86_omf", "x86_mscoff"];
393 			}
394 		}
395 		else if (compilerName == "gdc")
396 		{
397 			// https://github.com/dlang/dub/blob/master/source/dub/compilers/gdc.d#L69
398 			types ~= ["arm", "arm_thumb"];
399 		}
400 		else if (compilerName == "ldc")
401 		{
402 			// https://github.com/dlang/dub/blob/master/source/dub/compilers/ldc.d#L80
403 			types ~= ["aarch64", "powerpc64"];
404 		}
405 
406 		return types.data;
407 	}
408 
409 	/// ditto
410 	ArchType[] extendedArchTypes() const @property
411 	{
412 		auto types = appender!(ArchType[]);
413 		string compilerName = _compiler.name;
414 
415 		if (compilerName == "dmd")
416 		{
417 			types ~= [
418 				ArchType("", "(compiler default)"),
419 				ArchType("x86_64"),
420 				ArchType("x86")
421 			];
422 			// https://github.com/dlang/dub/blob/master/source/dub/compilers/dmd.d#L110
423 			version (Windows)
424 			{
425 				types ~= [ArchType("x86_omf"), ArchType("x86_mscoff")];
426 			}
427 		}
428 		else if (compilerName == "gdc")
429 		{
430 			// https://github.com/dlang/dub/blob/master/source/dub/compilers/gdc.d#L69
431 			types ~= [
432 				ArchType("", "(compiler default)"),
433 				ArchType("x86_64", "64-bit (current platform)"),
434 				ArchType("x86", "32-bit (current platform)"),
435 				ArchType("arm"),
436 				ArchType("arm_thumb")
437 			];
438 		}
439 		else if (compilerName == "ldc")
440 		{
441 			types ~= [
442 				ArchType("", "(compiler default)"),
443 				ArchType("x86_64"),
444 				ArchType("x86")
445 			];
446 			// https://github.com/dlang/dub/blob/master/source/dub/compilers/ldc.d#L80
447 			types ~= [
448 				ArchType("aarch64"),
449 				ArchType("powerpc64"),
450 				ArchType("wasm32-unknown-unknown-wasm", "WebAssembly")
451 			];
452 		}
453 
454 		return types.data;
455 	}
456 
457 	/// Returns the current selected arch type, or empty string for compiler default.
458 	string archType() const @property
459 	{
460 		return _archType;
461 	}
462 
463 	/// Selects a new arch type and updates the import paths accordingly
464 	/// Returns: `false` if there are no import paths in the new arch type
465 	bool setArchType(JSONValue request)
466 	{
467 		enforce(request.type == JSONType.object && "arch-type" in request, "arch-type not in request");
468 		auto type = request["arch-type"].fromJSON!string;
469 
470 		try
471 		{
472 			_platform = _compiler.determinePlatform(_settings, _compilerBinaryName, type);
473 		}
474 		catch (Exception e)
475 		{
476 			return false;
477 		}
478 
479 		_archType = type;
480 		return updateImportPaths(false);
481 	}
482 
483 	/// Returns the current selected build type
484 	string buildType() const @property
485 	{
486 		return _buildType;
487 	}
488 
489 	/// Selects a new build type and updates the import paths accordingly
490 	/// Returns: `false` if there are no import paths in the new build type
491 	bool setBuildType(JSONValue request)
492 	{
493 		enforce(request.type == JSONType.object && "build-type" in request, "build-type not in request");
494 		auto type = request["build-type"].fromJSON!string;
495 		if (buildTypes.canFind(type))
496 		{
497 			_buildType = type;
498 			return updateImportPaths(false);
499 		}
500 		else
501 		{
502 			return false;
503 		}
504 	}
505 
506 	/// Returns the current selected compiler
507 	string compiler() const @property
508 	{
509 		return _compilerBinaryName;
510 	}
511 
512 	/// Selects a new compiler for building
513 	/// Returns: `false` if the compiler does not exist or some setting is
514 	/// invalid.
515 	///
516 	/// If the current architecture does not exist with this compiler it will be
517 	/// reset to the compiler default. (empty string)
518 	bool setCompiler(string compiler)
519 	{
520 		try
521 		{
522 			_compilerBinaryName = compiler;
523 			_compiler = getCompiler(compiler); // make sure it gets a valid compiler
524 		}
525 		catch (Exception e)
526 		{
527 			return false;
528 		}
529 
530 		try
531 		{
532 			_platform = _compiler.determinePlatform(_settings, _compilerBinaryName, _archType);
533 		}
534 		catch (UnsupportedArchitectureException e)
535 		{
536 			if (_archType.length)
537 			{
538 				_archType = "";
539 				return setCompiler(compiler);
540 			}
541 			return false;
542 		}
543 
544 		_settingsTemplate.getPlatformSettings(_settings, _platform,
545 			_dub.project.rootPackage.path);
546 		return _compiler !is null;
547 	}
548 
549 	/// Returns the project name
550 	string name() const @property
551 	{
552 		return _dub.projectName;
553 	}
554 
555 	/// Returns the project path
556 	auto path() const @property
557 	{
558 		return _dub.projectPath;
559 	}
560 
561 	/// Returns whether there is a target set to build. If this is false then build will throw an exception.
562 	bool canBuild() const @property
563 	{
564 		if (_settings.targetType == TargetType.none || _settings.targetType == TargetType.sourceLibrary
565 				|| !_dub.project.configurations.canFind(_configuration))
566 			return false;
567 		return true;
568 	}
569 
570 	/// Asynchroniously builds the project WITHOUT OUTPUT. This is intended for linting code and showing build errors quickly inside the IDE.
571 	Future!(BuildIssue[]) build()
572 	{
573 		import std.process : thisProcessID;
574 		import std.file : tempDir;
575 		import std.random : uniform;
576 
577 		validateBuildConfiguration();
578 
579 		// copy to this thread
580 		auto compiler = _compiler;
581 		auto buildPlatform = _platform;
582 
583 		GeneratorSettings settings;
584 		settings.platform = buildPlatform;
585 		settings.config = _configuration;
586 		settings.buildType = _buildType;
587 		settings.compiler = compiler;
588 		settings.buildSettings = _settings;
589 
590 		auto ret = new typeof(return);
591 		new Thread({
592 			try
593 			{
594 				auto issues = appender!(BuildIssue[]);
595 
596 				settings.compileCallback = (status, output) {
597 					string[] lines = output.splitLines;
598 					foreach (line; lines)
599 					{
600 						auto match = line.matchFirst(errorFormat);
601 						if (match)
602 						{
603 							issues ~= BuildIssue(match[2].to!int, match[3].toOr!int(0),
604 								match[1], match[4].to!ErrorType, match[5]);
605 						}
606 						else
607 						{
608 							auto contMatch = line.matchFirst(errorFormatCont);
609 							if (issues.data.length && contMatch)
610 							{
611 								issues ~= BuildIssue(contMatch[2].to!int,
612 									contMatch[3].toOr!int(1), contMatch[1],
613 									issues.data[$ - 1].type, contMatch[4], true);
614 							}
615 							else if (line.canFind("is deprecated"))
616 							{
617 								auto deprMatch = line.matchFirst(deprecationFormat);
618 								if (deprMatch)
619 								{
620 									issues ~= BuildIssue(deprMatch[2].to!int, deprMatch[3].toOr!int(1),
621 										deprMatch[1], ErrorType.Deprecation,
622 										deprMatch[4] ~ " is deprecated, use " ~ deprMatch[5] ~ " instead.");
623 								}
624 							}
625 						}
626 					}
627 				};
628 				try
629 				{
630 					import workspaced.dub.lintgenerator : DubLintGenerator;
631 
632 					new DubLintGenerator(_dub.project).generate(settings);
633 				}
634 				catch (Exception e)
635 				{
636 					if (!e.msg.matchFirst(harmlessExceptionFormat))
637 						throw e;
638 				}
639 				ret.finish(issues.data);
640 			}
641 			catch (Throwable t)
642 			{
643 				ret.error(t);
644 			}
645 		}).start();
646 		return ret;
647 	}
648 
649 	/// Converts the root package recipe to another format.
650 	/// Params:
651 	///     format = either "json" or "sdl".
652 	string convertRecipe(string format)
653 	{
654 		import dub.recipe.io : serializePackageRecipe;
655 		import std.array : appender;
656 
657 		auto dst = appender!string;
658 		serializePackageRecipe(dst, _dub.project.rootPackage.rawRecipe, "dub." ~ format);
659 		return dst.data;
660 	}
661 
662 	/// Tries to find a suitable code byte range where a given dub build issue
663 	/// applies to.
664 	/// Returns: `[pos, pos]` if not found, otherwise range in bytes which might
665 	/// not contain the position at all.
666 	int[2] resolveDiagnosticRange(scope const(char)[] code, int position,
667 			scope const(char)[] diagnostic)
668 	{
669 		import dparse.lexer : getTokensForParser, LexerConfig;
670 		import dparse.parser : parseModule;
671 		import dparse.rollback_allocator : RollbackAllocator;
672 		import workspaced.dub.diagnostics : resolveDubDiagnosticRange;
673 
674 		LexerConfig config;
675 		RollbackAllocator rba;
676 		auto tokens = getTokensForParser(cast(ubyte[]) code, config, &workspaced.stringCache);
677 		auto parsed = parseModule(tokens, "equal_finder.d", &rba);
678 
679 		return resolveDubDiagnosticRange(code, tokens, parsed, position, diagnostic);
680 	}
681 
682 private:
683 	Dub _dub;
684 	bool _dubRunning = false;
685 	string _configuration;
686 	string _archType = "";
687 	string _buildType = "debug";
688 	string _compilerBinaryName;
689 	Compiler _compiler;
690 	BuildSettingsTemplate _settingsTemplate;
691 	BuildSettings _settings;
692 	BuildPlatform _platform;
693 	string[] _importPaths, _stringImportPaths, _importFiles, _versions, _debugVersions;
694 }
695 
696 ///
697 enum ErrorType : ubyte
698 {
699 	///
700 	Error = 0,
701 	///
702 	Warning = 1,
703 	///
704 	Deprecation = 2
705 }
706 
707 /// Returned by build
708 struct BuildIssue
709 {
710 	///
711 	int line, column;
712 	///
713 	string file;
714 	/// The error type (Error/Warning/Deprecation) outputted by dmd or inherited from the last error if this is additional information of the last issue. (indicated by cont)
715 	ErrorType type;
716 	///
717 	string text;
718 	/// true if this is additional error information for the last error.
719 	bool cont;
720 }
721 
722 /// returned by rootPackageBuildSettings
723 struct PackageBuildSettings
724 {
725 	/// construct from dub build settings
726 	this(BuildSettings dubBuildSettings, string packagePath, string packageName, string recipePath)
727 	{
728 		foreach (i, ref val; this.tupleof[0 .. __IGNORE_TRAIL])
729 		{
730 			enum name = __traits(identifier, this.tupleof[i]);
731 			static if (__traits(hasMember, dubBuildSettings, name))
732 				val = __traits(getMember, dubBuildSettings, name);
733 		}
734 		this.packagePath = packagePath;
735 		this.packageName = packageName;
736 		this.recipePath = recipePath;
737 
738 		if (!targetName.length)
739 			targetName = packageName;
740 
741 		version (Windows)
742 			targetName ~= ".exe";
743 
744 		this.targetType = dubBuildSettings.targetType.to!string;
745 		foreach (enumMember; __traits(allMembers, BuildOption))
746 		{
747 			enum value = __traits(getMember, BuildOption, enumMember);
748 			if (value != 0 && dubBuildSettings.options.opDispatch!enumMember)
749 				this.buildOptions ~= enumMember;
750 		}
751 		foreach (enumMember; __traits(allMembers, BuildRequirement))
752 		{
753 			enum value = __traits(getMember, BuildRequirement, enumMember);
754 			if (value != 0 && dubBuildSettings.requirements.opDispatch!enumMember)
755 				this.buildRequirements ~= enumMember;
756 		}
757 	}
758 
759 	string packagePath;
760 	string packageName;
761 	string recipePath;
762 
763 	string targetPath; /// same as dub BuildSettings
764 	string targetName; /// same as dub BuildSettings
765 	string workingDirectory; /// same as dub BuildSettings
766 	string mainSourceFile; /// same as dub BuildSettings
767 	string[] dflags; /// same as dub BuildSettings
768 	string[] lflags; /// same as dub BuildSettings
769 	string[] libs; /// same as dub BuildSettings
770 	string[] linkerFiles; /// same as dub BuildSettings
771 	string[] sourceFiles; /// same as dub BuildSettings
772 	string[] copyFiles; /// same as dub BuildSettings
773 	string[] extraDependencyFiles; /// same as dub BuildSettings
774 	string[] versions; /// same as dub BuildSettings
775 	string[] debugVersions; /// same as dub BuildSettings
776 	string[] versionFilters; /// same as dub BuildSettings
777 	string[] debugVersionFilters; /// same as dub BuildSettings
778 	string[] importPaths; /// same as dub BuildSettings
779 	string[] stringImportPaths; /// same as dub BuildSettings
780 	string[] importFiles; /// same as dub BuildSettings
781 	string[] stringImportFiles; /// same as dub BuildSettings
782 	string[] preGenerateCommands; /// same as dub BuildSettings
783 	string[] postGenerateCommands; /// same as dub BuildSettings
784 	string[] preBuildCommands; /// same as dub BuildSettings
785 	string[] postBuildCommands; /// same as dub BuildSettings
786 	string[] preRunCommands; /// same as dub BuildSettings
787 	string[] postRunCommands; /// same as dub BuildSettings
788 
789 	private enum __IGNORE_TRAIL = 2; // number of ignored settings below this line
790 
791 	string targetType; /// same as dub BuildSettings
792 	string[] buildOptions; /// same as dub BuildSettings
793 	string[] buildRequirements; /// same as dub BuildSettings
794 }
795 
796 private:
797 
798 T toOr(T)(string s, T defaultValue)
799 {
800 	if (!s || !s.length)
801 		return defaultValue;
802 	return s.to!T;
803 }
804 
805 enum harmlessExceptionFormat = ctRegex!(`failed with exit code`, "g");
806 enum errorFormat = ctRegex!(`(.*?)\((\d+)(?:,(\d+))?\): (Deprecation|Warning|Error): (.*)`, "gi");
807 enum errorFormatCont = ctRegex!(`(.*?)\((\d+)(?:,(\d+))?\):[ ]{6,}(.*)`, "g");
808 enum deprecationFormat = ctRegex!(
809 			`(.*?)\((\d+)(?:,(\d+))?\): (.*?) is deprecated, use (.*?) instead.$`, "g");
810 
811 struct DubPackageInfo
812 {
813 	string[string] dependencies;
814 	string ver;
815 	string name;
816 	string path;
817 	string description;
818 	string homepage;
819 	const(string)[] authors;
820 	string copyright;
821 	string license;
822 	DubPackageInfo[] subPackages;
823 
824 	void fill(in PackageRecipe recipe)
825 	{
826 		description = recipe.description;
827 		homepage = recipe.homepage;
828 		authors = recipe.authors;
829 		copyright = recipe.copyright;
830 		license = recipe.license;
831 
832 		foreach (subpackage; recipe.subPackages)
833 		{
834 			DubPackageInfo info;
835 			info.ver = subpackage.recipe.version_;
836 			info.name = subpackage.recipe.name;
837 			info.path = subpackage.path;
838 			info.fill(subpackage.recipe);
839 		}
840 	}
841 }
842 
843 DubPackageInfo getInfo(in Package dep)
844 {
845 	DubPackageInfo info;
846 	info.name = dep.name;
847 	info.ver = dep.version_.toString;
848 	info.path = dep.path.toString;
849 	info.fill(dep.recipe);
850 	foreach (subDep; dep.getAllDependencies())
851 	{
852 		info.dependencies[subDep.name] = subDep.spec.toString;
853 	}
854 	return info;
855 }
856 
857 auto listDependencies(scope const Project project)
858 {
859 	auto deps = project.dependencies;
860 	DubPackageInfo[] dependencies;
861 	if (deps is null)
862 		return dependencies;
863 	foreach (dep; deps)
864 	{
865 		dependencies ~= getInfo(dep);
866 	}
867 	return dependencies;
868 }
869 
870 string[] listDependencies(scope const Package pkg)
871 {
872 	auto deps = pkg.getAllDependencies();
873 	string[] dependencies;
874 	if (deps is null)
875 		return dependencies;
876 	foreach (dep; deps)
877 		dependencies ~= dep.name;
878 	return dependencies;
879 }
880 
881 ///
882 struct ArchType
883 {
884 	/// Value to pass into other calls
885 	string value;
886 	/// UI label override or null if none
887 	string label;
888 }