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