1 /**
2 	Stuff with dependencies.
3 
4 	Copyright: © 2012-2013 Matthias Dondorff
5 	License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file.
6 	Authors: Matthias Dondorff
7 */
8 module dub.package_;
9 
10 import dub.compilers.compiler;
11 import dub.dependency;
12 import dub.internal.utils;
13 import dub.internal.vibecompat.core.log;
14 import dub.internal.vibecompat.core.file;
15 import dub.internal.vibecompat.data.json;
16 import dub.internal.vibecompat.inet.url;
17 
18 import std.algorithm;
19 import std.array;
20 import std.conv;
21 import std.exception;
22 import std.file;
23 import std.range;
24 import std.string;
25 import std.traits : EnumMembers;
26 
27 enum PackageJsonFilename = "package.json";
28 
29 
30 /// Representing a downloded / cached package, usually constructed from a json object.
31 /// Documentation of the package.json can be found at 
32 /// http://registry.vibed.org/package-format
33 class Package {
34 	static struct LocalPackageDef { string name; Version version_; Path path; }
35 
36 	private {
37 		Path m_path;
38 		PackageInfo m_info;
39 		Package m_parentPackage;
40 		Package[] m_subPackages;
41 	}
42 
43 	this(Path root, Package parent = null)
44 	{
45 		this(jsonFromFile(root ~ PackageJsonFilename), root, parent);
46 	}
47 
48 	this(Json packageInfo, Path root = Path(), Package parent = null)
49 	{
50 		m_parentPackage = parent;
51 		m_path = root;
52 
53 		// check for default string import folders
54 		foreach(defvf; ["views"]){
55 			auto p = m_path ~ defvf;
56 			if( existsFile(p) )
57 				m_info.buildSettings.stringImportPaths[""] ~= defvf;
58 		}
59 
60 		string app_main_file, lib_main_file;
61 		auto pkg_name = packageInfo.name.get!string();
62 
63 		// check for default source folders
64 		foreach(defsf; ["source/", "src/"]){
65 			auto p = m_path ~ defsf;
66 			if( existsFile(p) ){
67 				m_info.buildSettings.sourcePaths[""] ~= defsf;
68 				m_info.buildSettings.importPaths[""] ~= defsf;
69 				foreach (fil; ["app.d", "main.d", pkg_name ~ "/main.d", pkg_name ~ "/" ~ pkg_name ~ ".d"])
70 					if (existsFile(p ~ fil)) {
71 						app_main_file = Path(defsf ~ fil).toNativeString();
72 						break;
73 					}
74 				foreach (fil; [pkg_name ~ "/all.d", pkg_name ~ "/package.d", pkg_name ~ "/" ~ pkg_name ~ ".d"])
75 					if (existsFile(p ~ fil)) {
76 						lib_main_file = Path(defsf ~ fil).toNativeString();
77 						break;
78 					}
79 			}
80 		}
81 
82 		// parse the JSON description
83 		{
84 			scope(failure) logError("Failed to parse package description in %s", root.toNativeString());
85 			m_info.parseJson(packageInfo);
86 
87 			// try to run git to determine the version of the package if no explicit version was given
88 			if (m_info.version_.length == 0 && !parent) {
89 				import std.process;
90 				try {
91 					auto branch = execute(["git", "--git-dir="~(root~".git").toNativeString(), "rev-parse", "--abbrev-ref", "HEAD"]);
92 					enforce(branch.status == 0, "git rev-parse failed: " ~ branch.output);
93 					if (branch.output.strip() == "HEAD") {
94 						//auto ver = execute("git",)
95 						enforce(false, "oops");
96 					} else {
97 						m_info.version_ = "~" ~ branch.output.strip();
98 					}
99 				} catch (Exception e) {
100 					logDebug("Failed to run git: %s", e.msg);
101 				}
102 
103 				if (m_info.version_.length == 0) {
104 					logDiagnostic("Note: Failed to determine version of package %s at %s. Assuming ~master.", m_info.name, this.path.toNativeString());
105 					m_info.version_ = "~master";
106 				} else logDiagnostic("Determined package version using GIT: %s %s", m_info.name, m_info.version_);
107 			}
108 		}
109 
110 		// generate default configurations if none are defined
111 		if (m_info.configurations.length == 0) {
112 			if (m_info.buildSettings.targetType == TargetType.executable) {
113 				BuildSettingsTemplate app_settings;
114 				app_settings.targetType = TargetType.executable;
115 				if (m_info.buildSettings.mainSourceFile.empty) app_settings.mainSourceFile = app_main_file;
116 				m_info.configurations ~= ConfigurationInfo("application", app_settings);
117 			} else if (m_info.buildSettings.targetType != TargetType.none) {
118 				BuildSettingsTemplate lib_settings;
119 				lib_settings.targetType = m_info.buildSettings.targetType == TargetType.autodetect ? TargetType.library : m_info.buildSettings.targetType;
120 
121 				if (m_info.buildSettings.targetType == TargetType.autodetect) {
122 					if (app_main_file.length && lib_main_file != app_main_file) {
123 						lib_settings.excludedSourceFiles[""] ~= app_main_file;
124 
125 						BuildSettingsTemplate app_settings;
126 						app_settings.targetType = TargetType.executable;
127 						app_settings.mainSourceFile = app_main_file;
128 						m_info.configurations ~= ConfigurationInfo("application", app_settings);
129 					}
130 				}
131 
132 				if (m_info.buildSettings.mainSourceFile.empty) lib_settings.mainSourceFile = lib_main_file;
133 				m_info.configurations ~= ConfigurationInfo("library", lib_settings);
134 			}
135 		}
136 
137 		// load all sub packages defined in the package description
138 		foreach (p; packageInfo.subPackages.opt!(Json[]))
139 			m_subPackages ~= new Package(p, root, this);
140 	}
141 	
142 	@property string name()
143 	const {
144 		if (m_parentPackage) return m_parentPackage.name ~ ":" ~ m_info.name;
145 		else return m_info.name;
146 	}
147 	@property string vers() const { return m_parentPackage ? m_parentPackage.vers : m_info.version_; }
148 	@property Version ver() const { return Version(this.vers); }
149 	@property ref inout(PackageInfo) info() inout { return m_info; }
150 	@property Path path() const { return m_path; }
151 	@property Path packageInfoFile() const { return m_path ~ "package.json"; }
152 	@property const(Dependency[string]) dependencies() const { return m_info.dependencies; }
153 	@property inout(Package) basePackage() inout { return m_parentPackage ? m_parentPackage.basePackage : this; }
154 	@property inout(Package) parentPackage() inout { return m_parentPackage; }
155 	@property inout(Package)[] subPackages() inout { return m_subPackages; }
156 
157 	@property string[] configurations()
158 	const {
159 		auto ret = appender!(string[])();
160 		foreach( ref config; m_info.configurations )
161 			ret.put(config.name);
162 		return ret.data;
163 	}
164 
165 	inout(Package) getSubPackage(string name) inout {
166 		foreach (p; m_subPackages)
167 			if (p.name == this.name ~ ":" ~ name)
168 				return p;
169 		throw new Exception(format("Unknown sub package: %s:%s", this.name, name));
170 	}
171 
172 	void warnOnSpecialCompilerFlags()
173 	{
174 		// warn about use of special flags
175 		m_info.buildSettings.warnOnSpecialCompilerFlags(m_info.name, null);
176 		foreach (ref config; m_info.configurations)
177 			config.buildSettings.warnOnSpecialCompilerFlags(m_info.name, config.name);
178 	}
179 
180 	/// Returns all BuildSettings for the given platform and config.
181 	BuildSettings getBuildSettings(in BuildPlatform platform, string config)
182 	const {
183 		BuildSettings ret;
184 		m_info.buildSettings.getPlatformSettings(ret, platform, this.path);
185 		bool found = false;
186 		foreach(ref conf; m_info.configurations){
187 			if( conf.name != config ) continue;
188 			conf.buildSettings.getPlatformSettings(ret, platform, this.path);
189 			found = true;
190 			break;
191 		}
192 		assert(found || config is null, "Unknown configuration for "~m_info.name~": "~config);
193 
194 		// construct default target name based on package name
195 		if( ret.targetName.empty ) ret.targetName = this.name.replace(":", "_");
196 
197 		// special support for DMD style flags
198 		getCompiler("dmd").extractBuildOptions(ret);
199 
200 		return ret;
201 	}
202 
203 	void addBuildTypeSettings(ref BuildSettings settings, in BuildPlatform platform, string build_type)
204 	const {
205 		if (build_type == "$DFLAGS") {
206 			import std.process;
207 			string dflags = environment.get("DFLAGS");
208 			settings.addDFlags(dflags.split());
209 			return;
210 		}
211 
212 		if (auto pbt = build_type in m_info.buildTypes) {
213 			logDiagnostic("Using custom build type '%s'.", build_type);
214 			pbt.getPlatformSettings(settings, platform, this.path);
215 		} else {
216 			with(BuildOptions) switch (build_type) {
217 				default: throw new Exception(format("Unknown build type for %s: '%s'", this.name, build_type));
218 				case "plain": break;
219 				case "debug": settings.addOptions(debugMode, debugInfo); break;
220 				case "release": settings.addOptions(releaseMode, optimize, inline); break;
221 				case "unittest": settings.addOptions(unittests, debugMode, debugInfo); break;
222 				case "docs": settings.addOptions(syntaxOnly); settings.addDFlags("-c", "-Dddocs"); break;
223 				case "ddox": settings.addOptions(syntaxOnly); settings.addDFlags("-c", "-Df__dummy.html", "-Xfdocs.json"); break;
224 				case "profile": settings.addOptions(profile, optimize, inline, debugInfo); break;
225 				case "cov": settings.addOptions(coverage, debugInfo); break;
226 				case "unittest-cov": settings.addOptions(unittests, coverage, debugMode, debugInfo); break;
227 			}
228 		}
229 	}
230 
231 	string getSubConfiguration(string config, in Package dependency, in BuildPlatform platform)
232 	const {
233 		bool found = false;
234 		foreach(ref c; m_info.configurations){
235 			if( c.name == config ){
236 				if( auto pv = dependency.name in c.buildSettings.subConfigurations ) return *pv;
237 				found = true;
238 				break;
239 			}
240 		}
241 		assert(found || config is null, "Invalid configuration \""~config~"\" for "~this.name);
242 		if( auto pv = dependency.name in m_info.buildSettings.subConfigurations ) return *pv;
243 		return null;
244 	}
245 
246 	/// Returns the default configuration to build for the given platform
247 	string getDefaultConfiguration(in BuildPlatform platform, bool allow_non_library = false)
248 	const {
249 		foreach (ref conf; m_info.configurations) {
250 			if (!conf.matchesPlatform(platform)) continue;
251 			if (!allow_non_library && conf.buildSettings.targetType == TargetType.executable) continue;
252 			return conf.name;
253 		}
254 		return null;
255 	}
256 
257 	/// Returns a list of configurations suitable for the given platform
258 	string[] getPlatformConfigurations(in BuildPlatform platform, bool is_main_package = false)
259 	const {
260 		auto ret = appender!(string[]);
261 		foreach(ref conf; m_info.configurations){
262 			if (!conf.matchesPlatform(platform)) continue;
263 			if (!is_main_package && conf.buildSettings.targetType == TargetType.executable) continue;
264 			ret ~= conf.name;
265 		}
266 		if (ret.data.length == 0) ret.put(null);
267 		return ret.data;
268 	}
269 
270 	/// Human readable information of this package and its dependencies.
271 	string generateInfoString() const {
272 		string s;
273 		s ~= m_info.name ~ ", version '" ~ m_info.version_ ~ "'";
274 		s ~= "\n  Dependencies:";
275 		foreach(string p, ref const Dependency v; m_info.dependencies)
276 			s ~= "\n    " ~ p ~ ", version '" ~ to!string(v) ~ "'";
277 		return s;
278 	}
279 	
280 	/// Writes the json file back to the filesystem
281 	void writeJson(Path path) {
282 		auto dstFile = openFile((path~PackageJsonFilename).toString(), FileMode.CreateTrunc);
283 		scope(exit) dstFile.close();
284 		dstFile.writePrettyJsonString(m_info.toJson());
285 		assert(false);
286 	}
287 
288 	bool hasDependency(string depname, string config)
289 	const {
290 		if (depname in m_info.buildSettings.dependencies) return true;
291 		foreach (ref c; m_info.configurations)
292 			if ((config.empty || c.name == config) && depname in c.buildSettings.dependencies)
293 				return true;
294 		return false;
295 	}
296 
297 	void describe(ref Json dst, BuildPlatform platform, string config)
298 	{
299 		dst.path = m_path.toNativeString();
300 		dst.name = this.name;
301 		dst["version"] = this.vers;
302 		dst.description = m_info.description;
303 		dst.homepage = m_info.homepage;
304 		dst.authors = m_info.authors.serializeToJson();
305 		dst.copyright = m_info.copyright;
306 		dst.license = m_info.license;
307 		dst.dependencies = m_info.dependencies.keys.serializeToJson();
308 
309 		// save build settings
310 		BuildSettings bs = getBuildSettings(platform, config);
311 
312 		foreach (string k, v; bs.serializeToJson()) dst[k] = v;
313 		dst.remove("requirements");
314 		dst.remove("sourceFiles");
315 		dst.remove("importFiles");
316 		dst.remove("stringImportFiles");
317 		dst.targetType = bs.targetType.to!string();
318 		if (dst.targetType != TargetType.none)
319 			dst.targetFileName = getTargetFileName(bs, platform);
320 
321 		// prettify build requirements output
322 		Json[] breqs;
323 		for (int i = 1; i <= BuildRequirements.max; i <<= 1)
324 			if (bs.requirements & i)
325 				breqs ~= Json(to!string(cast(BuildRequirements)i));
326 		dst.buildRequirements = breqs;
327 
328 		// prettify options output
329 		Json[] bopts;
330 		for (int i = 1; i <= BuildOptions.max; i <<= 1)
331 			if (bs.options & i)
332 				bopts ~= Json(to!string(cast(BuildOptions)i));
333 		dst.options = bopts;
334 
335 		// prettify files output
336 		Json[] files;
337 		foreach (f; bs.sourceFiles) {
338 			auto jf = Json.emptyObject;
339 			jf.path = f;
340 			jf["type"] = "source";
341 			files ~= jf;
342 		}
343 		foreach (f; bs.importFiles) {
344 			auto jf = Json.emptyObject;
345 			jf.path = f;
346 			jf["type"] = "import";
347 			files ~= jf;
348 		}
349 		foreach (f; bs.stringImportFiles) {
350 			auto jf = Json.emptyObject;
351 			jf.path = f;
352 			jf["type"] = "stringImport";
353 			files ~= jf;
354 		}
355 		dst.files = Json(files);
356 	}
357 }
358 
359 /// Specifying package information without any connection to a certain 
360 /// retrived package, like Package class is doing.
361 struct PackageInfo {
362 	string name;
363 	string version_;
364 	string description;
365 	string homepage;
366 	string[] authors;
367 	string copyright;
368 	string license;
369 	string[] ddoxFilterArgs;
370 	BuildSettingsTemplate buildSettings;
371 	ConfigurationInfo[] configurations;
372 	BuildSettingsTemplate[string] buildTypes;
373 
374 	@property const(Dependency)[string] dependencies()
375 	const {
376 		const(Dependency)[string] ret;
377 		foreach (n, d; this.buildSettings.dependencies)
378 			ret[n] = d;
379 		foreach (ref c; configurations)
380 			foreach (n, d; c.buildSettings.dependencies)
381 				ret[n] = d;
382 		return ret;
383 	}
384 
385 	inout(ConfigurationInfo) getConfiguration(string name)
386 	inout {
387 		foreach (c; configurations)
388 			if (c.name == name)
389 				return c;
390 		throw new Exception("Unknown configuration: "~name);
391 	}
392 
393 	void parseJson(Json json)
394 	{
395 		foreach( string field, value; json ){
396 			switch(field){
397 				default: break;
398 				case "name": this.name = value.get!string; break;
399 				case "version": this.version_ = value.get!string; break;
400 				case "description": this.description = value.get!string; break;
401 				case "homepage": this.homepage = value.get!string; break;
402 				case "authors": this.authors = deserializeJson!(string[])(value); break;
403 				case "copyright": this.copyright = value.get!string; break;
404 				case "license": this.license = value.get!string; break;
405 				case "-ddoxFilterArgs": this.ddoxFilterArgs = deserializeJson!(string[])(value); break;
406 				case "configurations": break; // handled below, after the global settings have been parsed
407 				case "buildTypes":
408 					foreach (string name, settings; value) {
409 						BuildSettingsTemplate bs;
410 						bs.parseJson(settings);
411 						buildTypes[name] = bs;
412 					}
413 					break;
414 			}
415 		}
416 
417 		// parse build settings
418 		this.buildSettings.parseJson(json);
419 
420 		if (auto pv = "configurations" in json) {
421 			TargetType deftargettp = TargetType.library;
422 			if (this.buildSettings.targetType != TargetType.autodetect)
423 				deftargettp = this.buildSettings.targetType;
424 
425 			foreach (settings; *pv) {
426 				ConfigurationInfo ci;
427 				ci.parseJson(settings, deftargettp);
428 				this.configurations ~= ci;
429 			}
430 		}
431 
432 		enforce(this.name.length > 0, "The package \"name\" field is missing or empty.");
433 	}
434 
435 	Json toJson()
436 	const {
437 		auto ret = buildSettings.toJson();
438 		ret.name = this.name;
439 		if( !this.version_.empty ) ret["version"] = this.version_;
440 		if( !this.description.empty ) ret.description = this.description;
441 		if( !this.homepage.empty ) ret.homepage = this.homepage;
442 		if( !this.authors.empty ) ret.authors = serializeToJson(this.authors);
443 		if( !this.copyright.empty ) ret.copyright = this.copyright;
444 		if( !this.license.empty ) ret.license = this.license;
445 		if( !this.ddoxFilterArgs.empty ) ret["-ddoxFilterArgs"] = this.ddoxFilterArgs.serializeToJson();
446 		if( this.configurations ){
447 			Json[] configs;
448 			foreach(config; this.configurations)
449 				configs ~= config.toJson();
450 			ret.configurations = configs;
451 		}
452 		return ret;
453 	}
454 }
455 
456 /// Bundles information about a build configuration.
457 struct ConfigurationInfo {
458 	string name;
459 	string[] platforms;
460 	BuildSettingsTemplate buildSettings;
461 
462 	this(string name, BuildSettingsTemplate build_settings)
463 	{
464 		enforce(!name.empty, "Configuration name is empty.");
465 		this.name = name;
466 		this.buildSettings = build_settings;
467 	}
468 
469 	void parseJson(Json json, TargetType default_target_type = TargetType.library)
470 	{
471 		this.buildSettings.targetType = default_target_type;
472 
473 		foreach(string name, value; json){
474 			switch(name){
475 				default: break;
476 				case "name":
477 					this.name = value.get!string();
478 					enforce(!this.name.empty, "Configurations must have a non-empty name.");
479 					break;
480 				case "platforms": this.platforms = deserializeJson!(string[])(value); break;
481 			}
482 		}
483 
484 		enforce(!this.name.empty, "Configuration is missing a name.");
485 
486 		BuildSettingsTemplate bs;
487 		this.buildSettings.parseJson(json);
488 	}
489 
490 	Json toJson()
491 	const {
492 		auto ret = buildSettings.toJson();
493 		ret.name = name;
494 		if( this.platforms.length ) ret.platforms = serializeToJson(platforms);
495 		return ret;
496 	}
497 
498 	bool matchesPlatform(in BuildPlatform platform)
499 	const {
500 		if( platforms.empty ) return true;
501 		foreach(p; platforms)
502 			if( platform.matchesSpecification("-"~p) )
503 				return true;
504 		return false;
505 	}
506 }
507 
508 /// This keeps general information about how to build a package.
509 /// It contains functions to create a specific BuildSetting, targeted at
510 /// a certain BuildPlatform.
511 struct BuildSettingsTemplate {
512 	Dependency[string] dependencies;
513 	TargetType targetType = TargetType.autodetect;
514 	string targetPath;
515 	string targetName;
516 	string workingDirectory;
517 	string mainSourceFile;
518 	string[string] subConfigurations;
519 	string[][string] dflags;
520 	string[][string] lflags;
521 	string[][string] libs;
522 	string[][string] sourceFiles;
523 	string[][string] sourcePaths;
524 	string[][string] excludedSourceFiles;
525 	string[][string] copyFiles;
526 	string[][string] versions;
527 	string[][string] debugVersions;
528 	string[][string] importPaths;
529 	string[][string] stringImportPaths;
530 	string[][string] preGenerateCommands;
531 	string[][string] postGenerateCommands;
532 	string[][string] preBuildCommands;
533 	string[][string] postBuildCommands;
534 	BuildRequirements[string] buildRequirements;
535 	BuildOptions[string] buildOptions;
536 
537 	void parseJson(Json json)
538 	{
539 		foreach(string name, value; json)
540 		{
541 			auto idx = std..string.indexOf(name, "-");
542 			string basename, suffix;
543 			if( idx >= 0 ) basename = name[0 .. idx], suffix = name[idx .. $];
544 			else basename = name;
545 			switch(basename){
546 				default: break;
547 				case "dependencies":
548 					foreach( string pkg, verspec; value ) {
549 						enforce(pkg !in this.dependencies, "The dependency '"~pkg~"' is specified more than once." );
550 						Dependency dep;
551 						if( verspec.type == Json.Type.object ){
552 							enforce("version" in verspec, "Package information provided for package " ~ pkg ~ " is missing a version field.");
553 							auto ver = verspec["version"].get!string;
554 							if( auto pp = "path" in verspec ) {
555 								// This enforces the "version" specifier to be a simple version, 
556 								// without additional range specifiers.
557 								dep = Dependency(Version(ver));
558 								dep.path = Path(verspec.path.get!string());
559 							} else {
560 								// Using the string to be able to specifiy a range of versions.
561 								dep = Dependency(ver);
562 							}
563 							if( auto po = "optional" in verspec ) {
564 								dep.optional = verspec.optional.get!bool();
565 							}
566 						} else {
567 							// canonical "package-id": "version"
568 							dep = Dependency(verspec.get!string());
569 						}
570 						this.dependencies[pkg] = dep;
571 					}
572 					break;
573 				case "targetType":
574 					enforce(suffix.empty, "targetType does not support platform customization.");
575 					targetType = value.get!string().to!TargetType();
576 					break;
577 				case "targetPath":
578 					enforce(suffix.empty, "targetPath does not support platform customization.");
579 					this.targetPath = value.get!string;
580 					if (this.workingDirectory is null) this.workingDirectory = this.targetPath;
581 					break;
582 				case "targetName":
583 					enforce(suffix.empty, "targetName does not support platform customization.");
584 					this.targetName = value.get!string;
585 					break;
586 				case "workingDirectory":
587 					enforce(suffix.empty, "workingDirectory does not support platform customization.");
588 					this.workingDirectory = value.get!string;
589 					break;
590 				case "mainSourceFile":
591 					enforce(suffix.empty, "mainSourceFile does not support platform customization.");
592 					this.mainSourceFile = value.get!string;
593 					break;
594 				case "subConfigurations":
595 					enforce(suffix.empty, "subConfigurations does not support platform customization.");
596 					this.subConfigurations = deserializeJson!(string[string])(value);
597 					break;
598 				case "dflags": this.dflags[suffix] = deserializeJson!(string[])(value); break;
599 				case "lflags": this.lflags[suffix] = deserializeJson!(string[])(value); break;
600 				case "libs": this.libs[suffix] = deserializeJson!(string[])(value); break;
601 				case "files":
602 				case "sourceFiles": this.sourceFiles[suffix] = deserializeJson!(string[])(value); break;
603 				case "sourcePaths": this.sourcePaths[suffix] = deserializeJson!(string[])(value); break;
604 				case "sourcePath": this.sourcePaths[suffix] ~= [value.get!string()]; break; // deprecated
605 				case "excludedSourceFiles": this.excludedSourceFiles[suffix] = deserializeJson!(string[])(value); break;
606 				case "copyFiles": this.copyFiles[suffix] = deserializeJson!(string[])(value); break;
607 				case "versions": this.versions[suffix] = deserializeJson!(string[])(value); break;
608 				case "debugVersions": this.debugVersions[suffix] = deserializeJson!(string[])(value); break;
609 				case "importPaths": this.importPaths[suffix] = deserializeJson!(string[])(value); break;
610 				case "stringImportPaths": this.stringImportPaths[suffix] = deserializeJson!(string[])(value); break;
611 				case "preGenerateCommands": this.preGenerateCommands[suffix] = deserializeJson!(string[])(value); break;
612 				case "postGenerateCommands": this.postGenerateCommands[suffix] = deserializeJson!(string[])(value); break;
613 				case "preBuildCommands": this.preBuildCommands[suffix] = deserializeJson!(string[])(value); break;
614 				case "postBuildCommands": this.postBuildCommands[suffix] = deserializeJson!(string[])(value); break;
615 				case "buildRequirements":
616 					BuildRequirements reqs;
617 					foreach (req; deserializeJson!(string[])(value))
618 						reqs |= to!BuildRequirements(req);
619 					this.buildRequirements[suffix] = reqs;
620 					break;
621 				case "buildOptions":
622 					BuildOptions options;
623 					foreach (opt; deserializeJson!(string[])(value))
624 						options |= to!BuildOptions(opt);
625 					this.buildOptions[suffix] = options;
626 					break;
627 			}
628 		}
629 	}
630 
631 	Json toJson()
632 	const {
633 		auto ret = Json.emptyObject;
634 		if( this.dependencies !is null ){
635 			auto deps = Json.emptyObject;
636 			foreach( pack, d; this.dependencies ){
637 				if( d.path.empty && !d.optional ){
638 					deps[pack] = d.toString();
639 				} else {
640 					auto vjson = Json.emptyObject;
641 					vjson["version"] = d.version_.toString();
642 					if (!d.path.empty) vjson["path"] = d.path.toString();
643 					if (d.optional) vjson["optional"] = true;
644 					deps[pack] = vjson;
645 				}
646 			}
647 			ret.dependencies = deps;
648 		}
649 		if (targetType != TargetType.autodetect) ret["targetType"] = targetType.to!string();
650 		if (!targetPath.empty) ret["targetPath"] = targetPath;
651 		if (!targetName.empty) ret["targetName"] = targetName;
652 		if (!workingDirectory.empty) ret["workingDirectory"] = workingDirectory;
653 		if (!mainSourceFile.empty) ret["mainSourceFile"] = mainSourceFile;
654 		foreach (suffix, arr; dflags) ret["dflags"~suffix] = serializeToJson(arr);
655 		foreach (suffix, arr; lflags) ret["lflags"~suffix] = serializeToJson(arr);
656 		foreach (suffix, arr; libs) ret["libs"~suffix] = serializeToJson(arr);
657 		foreach (suffix, arr; sourceFiles) ret["sourceFiles"~suffix] = serializeToJson(arr);
658 		foreach (suffix, arr; sourcePaths) ret["sourcePaths"~suffix] = serializeToJson(arr);
659 		foreach (suffix, arr; excludedSourceFiles) ret["excludedSourceFiles"~suffix] = serializeToJson(arr);
660 		foreach (suffix, arr; copyFiles) ret["copyFiles"~suffix] = serializeToJson(arr);
661 		foreach (suffix, arr; versions) ret["versions"~suffix] = serializeToJson(arr);
662 		foreach (suffix, arr; debugVersions) ret["debugVersions"~suffix] = serializeToJson(arr);
663 		foreach (suffix, arr; importPaths) ret["importPaths"~suffix] = serializeToJson(arr);
664 		foreach (suffix, arr; stringImportPaths) ret["stringImportPaths"~suffix] = serializeToJson(arr);
665 		foreach (suffix, arr; preGenerateCommands) ret["preGenerateCommands"~suffix] = serializeToJson(arr);
666 		foreach (suffix, arr; postGenerateCommands) ret["postGenerateCommands"~suffix] = serializeToJson(arr);
667 		foreach (suffix, arr; preBuildCommands) ret["preBuildCommands"~suffix] = serializeToJson(arr);
668 		foreach (suffix, arr; postBuildCommands) ret["postBuildCommands"~suffix] = serializeToJson(arr);
669 		foreach (suffix, arr; buildRequirements) {
670 			string[] val;
671 			foreach (i; [EnumMembers!BuildRequirements])
672 				if (arr & i) val ~= to!string(i);
673 			ret["buildRequirements"~suffix] = serializeToJson(val);
674 		}
675 		foreach (suffix, arr; buildOptions) {
676 			string[] val;
677 			foreach (i; [EnumMembers!BuildOptions])
678 				if (arr & i) val ~= to!string(i);
679 			ret["buildOptions"~suffix] = serializeToJson(val);
680 		}
681 		return ret;
682 	}
683 
684 	/// Constructs a BuildSettings object from this template.
685 	void getPlatformSettings(ref BuildSettings dst, in BuildPlatform platform, Path base_path)
686 	const {
687 		dst.targetType = this.targetType;
688 		if (!this.targetPath.empty) dst.targetPath = this.targetPath;
689 		if (!this.targetName.empty) dst.targetName = this.targetName;
690 		if (!this.workingDirectory.empty) dst.workingDirectory = this.workingDirectory;
691 		if (!this.mainSourceFile.empty) dst.mainSourceFile = this.mainSourceFile;
692 
693 		void collectFiles(string method)(in string[][string] paths_map, string pattern)
694 		{
695 			foreach (suffix, paths; paths_map) {
696 				if (!platform.matchesSpecification(suffix))
697 					continue;
698 
699 				foreach (spath; paths) {
700 					enforce(!spath.empty, "Paths must not be empty strings.");
701 					auto path = Path(spath);
702 					if (!path.absolute) path = base_path ~ path;
703 					if (!existsFile(path) || !isDir(path.toNativeString())) {
704 						logWarn("Invalid source/import path: %s", path.toNativeString());
705 						continue;
706 					}
707 
708 					foreach (d; dirEntries(path.toNativeString(), pattern, SpanMode.depth)) {
709 						if (isDir(d.name)) continue;
710 						auto src = Path(d.name).relativeTo(base_path);
711 						__traits(getMember, dst, method)(src.toNativeString());
712 					}
713 				}
714 			}
715 		}
716 
717 		// collect files from all source/import folders
718 		collectFiles!"addSourceFiles"(sourcePaths, "*.d");
719 		collectFiles!"addImportFiles"(importPaths, "*.{d,di}");
720 		dst.removeImportFiles(dst.sourceFiles);
721 		collectFiles!"addStringImportFiles"(stringImportPaths, "*");
722 
723 		getPlatformSetting!("dflags", "addDFlags")(dst, platform);
724 		getPlatformSetting!("lflags", "addLFlags")(dst, platform);
725 		getPlatformSetting!("libs", "addLibs")(dst, platform);
726 		getPlatformSetting!("sourceFiles", "addSourceFiles")(dst, platform);
727 		getPlatformSetting!("excludedSourceFiles", "removeSourceFiles")(dst, platform);
728 		getPlatformSetting!("copyFiles", "addCopyFiles")(dst, platform);
729 		getPlatformSetting!("versions", "addVersions")(dst, platform);
730 		getPlatformSetting!("debugVersions", "addDebugVersions")(dst, platform);
731 		getPlatformSetting!("importPaths", "addImportPaths")(dst, platform);
732 		getPlatformSetting!("stringImportPaths", "addStringImportPaths")(dst, platform);
733 		getPlatformSetting!("preGenerateCommands", "addPreGenerateCommands")(dst, platform);
734 		getPlatformSetting!("postGenerateCommands", "addPostGenerateCommands")(dst, platform);
735 		getPlatformSetting!("preBuildCommands", "addPreBuildCommands")(dst, platform);
736 		getPlatformSetting!("postBuildCommands", "addPostBuildCommands")(dst, platform);
737 		getPlatformSetting!("buildRequirements", "addRequirements")(dst, platform);
738 		getPlatformSetting!("buildOptions", "addOptions")(dst, platform);
739 	}
740 
741 	void getPlatformSetting(string name, string addname)(ref BuildSettings dst, in BuildPlatform platform)
742 	const {
743 		foreach(suffix, values; __traits(getMember, this, name)){
744 			if( platform.matchesSpecification(suffix) )
745 				__traits(getMember, dst, addname)(values);
746 		}
747 	}
748 
749 	void warnOnSpecialCompilerFlags(string package_name, string config_name)
750 	{
751 		auto nodef = false;
752 		auto noprop = false;
753 		foreach (req; this.buildRequirements) {
754 			if (req & BuildRequirements.noDefaultFlags) nodef = true;
755 			if (req & BuildRequirements.relaxProperties) noprop = true;
756 		}
757 
758 		if (noprop) {
759 			logWarn(`Warning: "buildRequirements": ["relaxProperties"] is deprecated and is now the default behavior. Note that the -property switch will probably be removed in future versions of DMD.`);
760 			logWarn("");
761 		}
762 
763 		if (nodef) {
764 			logWarn("Warning: This package uses the \"noDefaultFlags\" build requirement. Please use only for development purposes and not for released packages.");
765 			logWarn("");
766 		} else {
767 			string[] all_dflags;
768 			foreach (flags; this.dflags)
769 				all_dflags ~= flags;
770 			.warnOnSpecialCompilerFlags(all_dflags, package_name, config_name);
771 		}
772 	}
773 }
774 
775 /// Returns all package names, starting with the root package in [0].
776 string[] getSubPackagePath(string package_name)
777 {
778 	return package_name.split(":");
779 }
780 
781 /// Returns the name of the base package in the case of some sub package or the
782 /// package itself, if it is already a full package.
783 string getBasePackage(string package_name)
784 {
785 	return package_name.getSubPackagePath()[0];
786 }