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