1 /**
2 	SDL format support for PackageRecipe
3 
4 	Copyright: © 2014-2015 rejectedsoftware e.K.
5 	License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file.
6 	Authors: Sönke Ludwig
7 */
8 module dub.recipe.sdl;
9 
10 import dub.compilers.compiler;
11 import dub.dependency;
12 import dub.internal.sdlang;
13 import dub.internal.vibecompat.core.log;
14 import dub.internal.vibecompat.inet.path;
15 import dub.recipe.packagerecipe;
16 
17 import std.algorithm : map;
18 import std.array : array;
19 import std.conv;
20 import std.string : startsWith;
21 
22 
23 void parseSDL(ref PackageRecipe recipe, string sdl, string parent_name, string filename)
24 {
25 	parseSDL(recipe, parseSource(sdl, filename), parent_name);
26 }
27 
28 void parseSDL(ref PackageRecipe recipe, Tag sdl, string parent_name)
29 {
30 	Tag[] subpacks;
31 	Tag[] configs;
32 
33 	// parse top-level fields
34 	foreach (n; sdl.all.tags) {
35 		enforceSDL(n.name.length > 0, "Anonymous tags are not allowed at the root level.", n);
36 		switch (n.fullName) {
37 			default: break;
38 			case "name": recipe.name = n.stringTagValue; break;
39 			case "version": recipe.version_ = n.stringTagValue; break;
40 			case "description": recipe.description = n.stringTagValue; break;
41 			case "homepage": recipe.homepage = n.stringTagValue; break;
42 			case "authors": recipe.authors ~= n.stringArrayTagValue; break;
43 			case "copyright": recipe.copyright = n.stringTagValue; break;
44 			case "license": recipe.license = n.stringTagValue; break;
45 			case "subPackage": subpacks ~= n; break;
46 			case "configuration": configs ~= n; break;
47 			case "buildType":
48 				auto name = n.stringTagValue(true);
49 				BuildSettingsTemplate bt;
50 				parseBuildSettings(n, bt, parent_name);
51 				recipe.buildTypes[name] = bt;
52 				break;
53 			case "toolchainRequirements":
54 				parseToolchainRequirements(recipe.toolchainRequirements, n);
55 				break;
56 			case "x:ddoxFilterArgs": recipe.ddoxFilterArgs ~= n.stringArrayTagValue; break;
57 			case "x:ddoxTool": recipe.ddoxTool = n.stringTagValue; break;
58 		}
59 	}
60 
61 	enforceSDL(recipe.name.length > 0, "The package \"name\" field is missing or empty.", sdl);
62 	string full_name = parent_name.length ? parent_name ~ ":" ~ recipe.name : recipe.name;
63 
64 	// parse general build settings
65 	parseBuildSettings(sdl, recipe.buildSettings, full_name);
66 
67 	// determine default target type for configurations
68 	auto defttype = recipe.buildSettings.targetType;
69 	if (defttype == TargetType.autodetect)
70 		defttype = TargetType.library;
71 
72 	// parse configurations
73 	recipe.configurations.length = configs.length;
74 	foreach (i, n; configs) {
75 		recipe.configurations[i].buildSettings.targetType = defttype;
76 		parseConfiguration(n, recipe.configurations[i], full_name);
77 	}
78 
79 	// finally parse all sub packages
80 	recipe.subPackages.length = subpacks.length;
81 	foreach (i, n; subpacks) {
82 		if (n.values.length) {
83 			recipe.subPackages[i].path = n.stringTagValue;
84 		} else {
85 			enforceSDL(n.attributes.length == 0, "No attributes allowed for inline sub package definitions.", n);
86 			parseSDL(recipe.subPackages[i].recipe, n, full_name);
87 		}
88 	}
89 }
90 
91 Tag toSDL(in ref PackageRecipe recipe)
92 {
93 	Tag ret = new Tag;
94 	void add(T)(string field, T value) { ret.add(new Tag(null, field, [Value(value)])); }
95 	add("name", recipe.name);
96 	if (recipe.version_.length) add("version", recipe.version_);
97 	if (recipe.description.length) add("description", recipe.description);
98 	if (recipe.homepage.length) add("homepage", recipe.homepage);
99 	if (recipe.authors.length) ret.add(new Tag(null, "authors", recipe.authors.map!(a => Value(a)).array));
100 	if (recipe.copyright.length) add("copyright", recipe.copyright);
101 	if (recipe.license.length) add("license", recipe.license);
102 	foreach (name, settings; recipe.buildTypes) {
103 		auto t = new Tag(null, "buildType", [Value(name)]);
104 		t.add(settings.toSDL());
105 		ret.add(t);
106 	}
107 	if (!recipe.toolchainRequirements.empty) {
108 		ret.add(toSDL(recipe.toolchainRequirements));
109 	}
110 	if (recipe.ddoxFilterArgs.length)
111 		ret.add(new Tag("x", "ddoxFilterArgs", recipe.ddoxFilterArgs.map!(a => Value(a)).array));
112 	if (recipe.ddoxTool.length) ret.add(new Tag("x", "ddoxTool", [Value(recipe.ddoxTool)]));
113 	ret.add(recipe.buildSettings.toSDL());
114 	foreach(config; recipe.configurations)
115 		ret.add(config.toSDL());
116 	foreach (i, subPackage; recipe.subPackages) {
117 		if (subPackage.path !is null) {
118 			add("subPackage", subPackage.path);
119 		} else {
120 			auto t = subPackage.recipe.toSDL();
121 			t.name = "subPackage";
122 			ret.add(t);
123 		}
124 	}
125 	return ret;
126 }
127 
128 private void parseBuildSettings(Tag settings, ref BuildSettingsTemplate bs, string package_name)
129 {
130 	foreach (setting; settings.all.tags)
131 		parseBuildSetting(setting, bs, package_name);
132 }
133 
134 private void parseBuildSetting(Tag setting, ref BuildSettingsTemplate bs, string package_name)
135 {
136 	switch (setting.fullName) {
137 		default: break;
138 		case "dependency": parseDependency(setting, bs, package_name); break;
139 		case "systemDependencies": bs.systemDependencies = setting.stringTagValue; break;
140 		case "targetType": bs.targetType = setting.stringTagValue.to!TargetType; break;
141 		case "targetName": bs.targetName = setting.stringTagValue; break;
142 		case "targetPath": bs.targetPath = setting.stringTagValue; break;
143 		case "workingDirectory": bs.workingDirectory = setting.stringTagValue; break;
144 		case "subConfiguration":
145 			auto args = setting.stringArrayTagValue;
146 			enforceSDL(args.length == 2, "Expecting package and configuration names as arguments.", setting);
147 			bs.subConfigurations[expandPackageName(args[0], package_name, setting)] = args[1];
148 			break;
149 		case "dflags": setting.parsePlatformStringArray(bs.dflags); break;
150 		case "lflags": setting.parsePlatformStringArray(bs.lflags); break;
151 		case "libs": setting.parsePlatformStringArray(bs.libs); break;
152 		case "sourceFiles": setting.parsePlatformStringArray(bs.sourceFiles); break;
153 		case "sourcePaths": setting.parsePlatformStringArray(bs.sourcePaths); break;
154 		case "excludedSourceFiles": setting.parsePlatformStringArray(bs.excludedSourceFiles); break;
155 		case "mainSourceFile": bs.mainSourceFile = setting.stringTagValue; break;
156 		case "copyFiles": setting.parsePlatformStringArray(bs.copyFiles); break;
157 		case "extraDependencyFiles": setting.parsePlatformStringArray(bs.extraDependencyFiles); break;
158 		case "versions": setting.parsePlatformStringArray(bs.versions); break;
159 		case "debugVersions": setting.parsePlatformStringArray(bs.debugVersions); break;
160 		case "x:versionFilters": setting.parsePlatformStringArray(bs.versionFilters); break;
161 		case "x:debugVersionFilters": setting.parsePlatformStringArray(bs.debugVersionFilters); break;
162 		case "importPaths": setting.parsePlatformStringArray(bs.importPaths); break;
163 		case "stringImportPaths": setting.parsePlatformStringArray(bs.stringImportPaths); break;
164 		case "preGenerateCommands": setting.parsePlatformStringArray(bs.preGenerateCommands); break;
165 		case "postGenerateCommands": setting.parsePlatformStringArray(bs.postGenerateCommands); break;
166 		case "preBuildCommands": setting.parsePlatformStringArray(bs.preBuildCommands); break;
167 		case "postBuildCommands": setting.parsePlatformStringArray(bs.postBuildCommands); break;
168 		case "preRunCommands": setting.parsePlatformStringArray(bs.preRunCommands); break;
169 		case "postRunCommands": setting.parsePlatformStringArray(bs.postRunCommands); break;
170 		case "buildRequirements": setting.parsePlatformEnumArray!BuildRequirement(bs.buildRequirements); break;
171 		case "buildOptions": setting.parsePlatformEnumArray!BuildOption(bs.buildOptions); break;
172 	}
173 }
174 
175 private void parseDependency(Tag t, ref BuildSettingsTemplate bs, string package_name)
176 {
177 	enforceSDL(t.values.length != 0, "Missing dependency name.", t);
178 	enforceSDL(t.values.length == 1, "Multiple dependency names.", t);
179 	auto pkg = expandPackageName(t.values[0].get!string, package_name, t);
180 	enforceSDL(pkg !in bs.dependencies, "The dependency '"~pkg~"' is specified more than once.", t);
181 
182 	Dependency dep = Dependency.any;
183 	auto attrs = t.attributes;
184 
185 	if ("path" in attrs) {
186 		if ("version" in attrs)
187 			logDiagnostic("Ignoring version specification (%s) for path based dependency %s", attrs["version"][0].value.get!string, attrs["path"][0].value.get!string);
188 		dep.versionSpec = "*";
189 		dep.path = NativePath(attrs["path"][0].value.get!string);
190 	} else {
191 		enforceSDL("version" in attrs, "Missing version specification.", t);
192 		dep.versionSpec = attrs["version"][0].value.get!string;
193 	}
194 
195 	if ("optional" in attrs)
196 		dep.optional = attrs["optional"][0].value.get!bool;
197 
198 	if ("default" in attrs)
199 		dep.default_ = attrs["default"][0].value.get!bool;
200 
201 	bs.dependencies[pkg] = dep;
202 }
203 
204 private void parseConfiguration(Tag t, ref ConfigurationInfo ret, string package_name)
205 {
206 	ret.name = t.stringTagValue(true);
207 	foreach (f; t.tags) {
208 		switch (f.fullName) {
209 			default: parseBuildSetting(f, ret.buildSettings, package_name); break;
210 			case "platforms": ret.platforms ~= f.stringArrayTagValue; break;
211 		}
212 	}
213 }
214 
215 private Tag toSDL(in ref ConfigurationInfo config)
216 {
217 	auto ret = new Tag(null, "configuration", [Value(config.name)]);
218 	if (config.platforms.length) ret.add(new Tag(null, "platforms", config.platforms[].map!(p => Value(p)).array));
219 	ret.add(config.buildSettings.toSDL());
220 	return ret;
221 }
222 
223 private Tag[] toSDL(in ref BuildSettingsTemplate bs)
224 {
225 	Tag[] ret;
226 	void add(string name, string value, string namespace = null) { ret ~= new Tag(namespace, name, [Value(value)]); }
227 	void adda(string name, string suffix, in string[] values, string namespace = null) {
228 		ret ~= new Tag(namespace, name, values[].map!(v => Value(v)).array,
229 			suffix.length ? [new Attribute(null, "platform", Value(suffix[1 .. $]))] : null);
230 	}
231 
232 	string[] toNameArray(T, U)(U bits) if(is(T == enum)) {
233 		string[] ret;
234 		foreach (m; __traits(allMembers, T))
235 			if (bits & __traits(getMember, T, m))
236 				ret ~= m;
237 		return ret;
238 	}
239 
240 	foreach (pack, d; bs.dependencies) {
241 		Attribute[] attribs;
242 		if (!d.path.empty) attribs ~= new Attribute(null, "path", Value(d.path.toString()));
243 		else attribs ~= new Attribute(null, "version", Value(d.versionSpec));
244 		if (d.optional) attribs ~= new Attribute(null, "optional", Value(true));
245 		ret ~= new Tag(null, "dependency", [Value(pack)], attribs);
246 	}
247 	if (bs.systemDependencies !is null) add("systemDependencies", bs.systemDependencies);
248 	if (bs.targetType != TargetType.autodetect) add("targetType", bs.targetType.to!string());
249 	if (bs.targetPath.length) add("targetPath", bs.targetPath);
250 	if (bs.targetName.length) add("targetName", bs.targetName);
251 	if (bs.workingDirectory.length) add("workingDirectory", bs.workingDirectory);
252 	if (bs.mainSourceFile.length) add("mainSourceFile", bs.mainSourceFile);
253 	foreach (pack, conf; bs.subConfigurations) ret ~= new Tag(null, "subConfiguration", [Value(pack), Value(conf)]);
254 	foreach (suffix, arr; bs.dflags) adda("dflags", suffix, arr);
255 	foreach (suffix, arr; bs.lflags) adda("lflags", suffix, arr);
256 	foreach (suffix, arr; bs.libs) adda("libs", suffix, arr);
257 	foreach (suffix, arr; bs.sourceFiles) adda("sourceFiles", suffix, arr);
258 	foreach (suffix, arr; bs.sourcePaths) adda("sourcePaths", suffix, arr);
259 	foreach (suffix, arr; bs.excludedSourceFiles) adda("excludedSourceFiles", suffix, arr);
260 	foreach (suffix, arr; bs.copyFiles) adda("copyFiles", suffix, arr);
261 	foreach (suffix, arr; bs.extraDependencyFiles) adda("extraDependencyFiles", suffix, arr);
262 	foreach (suffix, arr; bs.versions) adda("versions", suffix, arr);
263 	foreach (suffix, arr; bs.debugVersions) adda("debugVersions", suffix, arr);
264 	foreach (suffix, arr; bs.versionFilters) adda("versionFilters", suffix, arr, "x");
265 	foreach (suffix, arr; bs.debugVersionFilters) adda("debugVersionFilters", suffix, arr, "x");
266 	foreach (suffix, arr; bs.importPaths) adda("importPaths", suffix, arr);
267 	foreach (suffix, arr; bs.stringImportPaths) adda("stringImportPaths", suffix, arr);
268 	foreach (suffix, arr; bs.preGenerateCommands) adda("preGenerateCommands", suffix, arr);
269 	foreach (suffix, arr; bs.postGenerateCommands) adda("postGenerateCommands", suffix, arr);
270 	foreach (suffix, arr; bs.preBuildCommands) adda("preBuildCommands", suffix, arr);
271 	foreach (suffix, arr; bs.postBuildCommands) adda("postBuildCommands", suffix, arr);
272 	foreach (suffix, arr; bs.preRunCommands) adda("preRunCommands", suffix, arr);
273 	foreach (suffix, arr; bs.postRunCommands) adda("postRunCommands", suffix, arr);
274 	foreach (suffix, bits; bs.buildRequirements) adda("buildRequirements", suffix, toNameArray!BuildRequirement(bits));
275 	foreach (suffix, bits; bs.buildOptions) adda("buildOptions", suffix, toNameArray!BuildOption(bits));
276 	return ret;
277 }
278 
279 private void parseToolchainRequirements(ref ToolchainRequirements tr, Tag tag)
280 {
281 	foreach (attr; tag.attributes)
282 		tr.addRequirement(attr.name, attr.value.get!string);
283 }
284 
285 private Tag toSDL(const ref ToolchainRequirements tr)
286 {
287 	Attribute[] attrs;
288 	if (tr.dub != Dependency.any) attrs ~= new Attribute("dub", Value(tr.dub.toString()));
289 	if (tr.frontend != Dependency.any) attrs ~= new Attribute("frontend", Value(tr.frontend.toString()));
290 	if (tr.dmd != Dependency.any) attrs ~= new Attribute("dmd", Value(tr.dmd.toString()));
291 	if (tr.ldc != Dependency.any) attrs ~= new Attribute("ldc", Value(tr.ldc.toString()));
292 	if (tr.gdc != Dependency.any) attrs ~= new Attribute("gdc", Value(tr.gdc.toString()));
293 	return new Tag(null, "toolchainRequirements", null, attrs);
294 }
295 
296 private string expandPackageName(string name, string parent_name, Tag tag)
297 {
298 	import std.algorithm : canFind;
299 	import std.string : format;
300 	if (name.startsWith(":")) {
301 		enforceSDL(!parent_name.canFind(':'), format("Short-hand packages syntax not allowed within sub packages: %s -> %s", parent_name, name), tag);
302 		return parent_name ~ name;
303 	} else return name;
304 }
305 
306 private string stringTagValue(Tag t, bool allow_child_tags = false)
307 {
308 	import std.string : format;
309 	enforceSDL(t.values.length > 0, format("Missing string value for '%s'.", t.fullName), t);
310 	enforceSDL(t.values.length == 1, format("Expected only one value for '%s'.", t.fullName), t);
311 	enforceSDL(t.values[0].peek!string !is null, format("Expected value of type string for '%s'.", t.fullName), t);
312 	enforceSDL(allow_child_tags || t.tags.length == 0, format("No child tags allowed for '%s'.", t.fullName), t);
313 	// Q: should attributes be disallowed, or just ignored for forward compatibility reasons?
314 	//enforceSDL(t.attributes.length == 0, format("No attributes allowed for '%s'.", t.fullName), t);
315 	return t.values[0].get!string;
316 }
317 
318 private string[] stringArrayTagValue(Tag t, bool allow_child_tags = false)
319 {
320 	import std.string : format;
321 	enforceSDL(allow_child_tags || t.tags.length == 0, format("No child tags allowed for '%s'.", t.fullName), t);
322 	// Q: should attributes be disallowed, or just ignored for forward compatibility reasons?
323 	//enforceSDL(t.attributes.length == 0, format("No attributes allowed for '%s'.", t.fullName), t);
324 
325 	string[] ret;
326 	foreach (v; t.values) {
327 		enforceSDL(t.values[0].peek!string !is null, format("Values for '%s' must be strings.", t.fullName), t);
328 		ret ~= v.get!string;
329 	}
330 	return ret;
331 }
332 
333 private void parsePlatformStringArray(Tag t, ref string[][string] dst)
334 {
335 	string platform;
336 	if ("platform" in t.attributes)
337 		platform = "-" ~ t.attributes["platform"][0].value.get!string;
338 	dst[platform] ~= t.values.map!(v => v.get!string).array;
339 }
340 
341 private void parsePlatformEnumArray(E, Es)(Tag t, ref Es[string] dst)
342 {
343 	string platform;
344 	if ("platform" in t.attributes)
345 		platform = "-" ~ t.attributes["platform"][0].value.get!string;
346 	foreach (v; t.values) {
347 		if (platform !in dst) dst[platform] = Es.init;
348 		dst[platform] |= v.get!string.to!E;
349 	}
350 }
351 
352 private void enforceSDL(bool condition, lazy string message, Tag tag, string file = __FILE__, int line = __LINE__)
353 {
354 	import std.string : format;
355 	if (!condition) {
356 		throw new Exception(format("%s(%s): Error: %s", tag.location.file, tag.location.line + 1, message), file, line);
357 	}
358 }
359 
360 
361 unittest { // test all possible fields
362 	auto sdl =
363 `name "projectname";
364 description "project description";
365 homepage "http://example.com"
366 authors "author 1" "author 2"
367 authors "author 3"
368 copyright "copyright string"
369 license "license string"
370 version "1.0.0"
371 subPackage {
372 	name "subpackage1"
373 }
374 subPackage {
375 	name "subpackage2"
376 	dependency "projectname:subpackage1" version="*"
377 }
378 subPackage "pathsp3"
379 configuration "config1" {
380 	platforms "windows" "linux"
381 	targetType "library"
382 }
383 configuration "config2" {
384 	platforms "windows-x86"
385 	targetType "executable"
386 }
387 buildType "debug" {
388 	dflags "-g" "-debug"
389 }
390 buildType "release" {
391 	dflags "-release" "-O"
392 }
393 toolchainRequirements dub="~>1.11.0" dmd="~>2.082"
394 x:ddoxFilterArgs "-arg1" "-arg2"
395 x:ddoxFilterArgs "-arg3"
396 x:ddoxTool "ddoxtool"
397 
398 dependency ":subpackage1" optional=false path="."
399 dependency "somedep" version="1.0.0" optional=true
400 systemDependencies "system dependencies"
401 targetType "executable"
402 targetName "target name"
403 targetPath "target path"
404 workingDirectory "working directory"
405 subConfiguration ":subpackage2" "library"
406 buildRequirements "allowWarnings" "silenceDeprecations"
407 buildOptions "verbose" "ignoreUnknownPragmas"
408 libs "lib1" "lib2"
409 libs "lib3"
410 sourceFiles "source1" "source2"
411 sourceFiles "source3"
412 sourcePaths "sourcepath1" "sourcepath2"
413 sourcePaths "sourcepath3"
414 excludedSourceFiles "excluded1" "excluded2"
415 excludedSourceFiles "excluded3"
416 mainSourceFile "main source"
417 copyFiles "copy1" "copy2"
418 copyFiles "copy3"
419 extraDependencyFiles "extradepfile1" "extradepfile2"
420 extraDependencyFiles "extradepfile3"
421 versions "version1" "version2"
422 versions "version3"
423 debugVersions "debug1" "debug2"
424 debugVersions "debug3"
425 x:versionFilters "version1" "version2"
426 x:versionFilters "version3"
427 x:versionFilters
428 x:debugVersionFilters "debug1" "debug2"
429 x:debugVersionFilters "debug3"
430 x:debugVersionFilters
431 importPaths "import1" "import2"
432 importPaths "import3"
433 stringImportPaths "string1" "string2"
434 stringImportPaths "string3"
435 preGenerateCommands "preg1" "preg2"
436 preGenerateCommands "preg3"
437 postGenerateCommands "postg1" "postg2"
438 postGenerateCommands "postg3"
439 preBuildCommands "preb1" "preb2"
440 preBuildCommands "preb3"
441 postBuildCommands "postb1" "postb2"
442 postBuildCommands "postb3"
443 preRunCommands "prer1" "prer2"
444 preRunCommands "prer3"
445 postRunCommands "postr1" "postr2"
446 postRunCommands "postr3"
447 dflags "df1" "df2"
448 dflags "df3"
449 lflags "lf1" "lf2"
450 lflags "lf3"
451 `;
452 	PackageRecipe rec1;
453 	parseSDL(rec1, sdl, null, "testfile");
454 	PackageRecipe rec;
455 	parseSDL(rec, rec1.toSDL(), null); // verify that all fields are serialized properly
456 
457 	assert(rec.name == "projectname");
458 	assert(rec.description == "project description");
459 	assert(rec.homepage == "http://example.com");
460 	assert(rec.authors == ["author 1", "author 2", "author 3"]);
461 	assert(rec.copyright == "copyright string");
462 	assert(rec.license == "license string");
463 	assert(rec.version_ == "1.0.0");
464 	assert(rec.subPackages.length == 3);
465 	assert(rec.subPackages[0].path == "");
466 	assert(rec.subPackages[0].recipe.name == "subpackage1");
467 	assert(rec.subPackages[1].path == "");
468 	assert(rec.subPackages[1].recipe.name == "subpackage2");
469 	assert(rec.subPackages[1].recipe.buildSettings.dependencies.length == 1);
470 	assert("projectname:subpackage1" in rec.subPackages[1].recipe.buildSettings.dependencies);
471 	assert(rec.subPackages[2].path == "pathsp3");
472 	assert(rec.configurations.length == 2);
473 	assert(rec.configurations[0].name == "config1");
474 	assert(rec.configurations[0].platforms == ["windows", "linux"]);
475 	assert(rec.configurations[0].buildSettings.targetType == TargetType.library);
476 	assert(rec.configurations[1].name == "config2");
477 	assert(rec.configurations[1].platforms == ["windows-x86"]);
478 	assert(rec.configurations[1].buildSettings.targetType == TargetType.executable);
479 	assert(rec.buildTypes.length == 2);
480 	assert(rec.buildTypes["debug"].dflags == ["": ["-g", "-debug"]]);
481 	assert(rec.buildTypes["release"].dflags == ["": ["-release", "-O"]]);
482 	assert(rec.toolchainRequirements.dub == Dependency("~>1.11.0"));
483 	assert(rec.toolchainRequirements.frontend == Dependency.any);
484 	assert(rec.toolchainRequirements.dmd == Dependency("~>2.82.0"));
485 	assert(rec.toolchainRequirements.ldc == Dependency.any);
486 	assert(rec.toolchainRequirements.gdc == Dependency.any);
487 	assert(rec.ddoxFilterArgs == ["-arg1", "-arg2", "-arg3"], rec.ddoxFilterArgs.to!string);
488 	assert(rec.ddoxTool == "ddoxtool");
489 	assert(rec.buildSettings.dependencies.length == 2);
490 	assert(rec.buildSettings.dependencies["projectname:subpackage1"].optional == false);
491 	assert(rec.buildSettings.dependencies["projectname:subpackage1"].path == NativePath("."));
492 	assert(rec.buildSettings.dependencies["somedep"].versionSpec == "1.0.0");
493 	assert(rec.buildSettings.dependencies["somedep"].optional == true);
494 	assert(rec.buildSettings.dependencies["somedep"].path.empty);
495 	assert(rec.buildSettings.systemDependencies == "system dependencies");
496 	assert(rec.buildSettings.targetType == TargetType.executable);
497 	assert(rec.buildSettings.targetName == "target name");
498 	assert(rec.buildSettings.targetPath == "target path");
499 	assert(rec.buildSettings.workingDirectory == "working directory");
500 	assert(rec.buildSettings.subConfigurations.length == 1);
501 	assert(rec.buildSettings.subConfigurations["projectname:subpackage2"] == "library");
502 	assert(rec.buildSettings.buildRequirements == ["": cast(BuildRequirements)(BuildRequirement.allowWarnings | BuildRequirement.silenceDeprecations)]);
503 	assert(rec.buildSettings.buildOptions == ["": cast(BuildOptions)(BuildOption.verbose | BuildOption.ignoreUnknownPragmas)]);
504 	assert(rec.buildSettings.libs == ["": ["lib1", "lib2", "lib3"]]);
505 	assert(rec.buildSettings.sourceFiles == ["": ["source1", "source2", "source3"]]);
506 	assert(rec.buildSettings.sourcePaths == ["": ["sourcepath1", "sourcepath2", "sourcepath3"]]);
507 	assert(rec.buildSettings.excludedSourceFiles == ["": ["excluded1", "excluded2", "excluded3"]]);
508 	assert(rec.buildSettings.mainSourceFile == "main source");
509 	assert(rec.buildSettings.copyFiles == ["": ["copy1", "copy2", "copy3"]]);
510 	assert(rec.buildSettings.extraDependencyFiles == ["": ["extradepfile1", "extradepfile2", "extradepfile3"]]);
511 	assert(rec.buildSettings.versions == ["": ["version1", "version2", "version3"]]);
512 	assert(rec.buildSettings.debugVersions == ["": ["debug1", "debug2", "debug3"]]);
513 	assert(rec.buildSettings.versionFilters == ["": ["version1", "version2", "version3"]]);
514 	assert(rec.buildSettings.debugVersionFilters == ["": ["debug1", "debug2", "debug3"]]);
515 	assert(rec.buildSettings.importPaths == ["": ["import1", "import2", "import3"]]);
516 	assert(rec.buildSettings.stringImportPaths == ["": ["string1", "string2", "string3"]]);
517 	assert(rec.buildSettings.preGenerateCommands == ["": ["preg1", "preg2", "preg3"]]);
518 	assert(rec.buildSettings.postGenerateCommands == ["": ["postg1", "postg2", "postg3"]]);
519 	assert(rec.buildSettings.preBuildCommands == ["": ["preb1", "preb2", "preb3"]]);
520 	assert(rec.buildSettings.postBuildCommands == ["": ["postb1", "postb2", "postb3"]]);
521 	assert(rec.buildSettings.preRunCommands == ["": ["prer1", "prer2", "prer3"]]);
522 	assert(rec.buildSettings.postRunCommands == ["": ["postr1", "postr2", "postr3"]]);
523 	assert(rec.buildSettings.dflags == ["": ["df1", "df2", "df3"]]);
524 	assert(rec.buildSettings.lflags == ["": ["lf1", "lf2", "lf3"]]);
525 }
526 
527 unittest { // test platform identifiers
528 	auto sdl =
529 `name "testproject"
530 dflags "-a" "-b" platform="windows-x86"
531 dflags "-c" platform="windows-x86"
532 dflags "-e" "-f"
533 dflags "-g"
534 dflags "-h" "-i" platform="linux"
535 dflags "-j" platform="linux"
536 `;
537 	PackageRecipe rec;
538 	parseSDL(rec, sdl, null, "testfile");
539 	assert(rec.buildSettings.dflags.length == 3);
540 	assert(rec.buildSettings.dflags["-windows-x86"] == ["-a", "-b", "-c"]);
541 	assert(rec.buildSettings.dflags[""] == ["-e", "-f", "-g"]);
542 	assert(rec.buildSettings.dflags["-linux"] == ["-h", "-i", "-j"]);
543 }
544 
545 unittest { // test for missing name field
546 	import std.exception;
547 	auto sdl = `description "missing name"`;
548 	PackageRecipe rec;
549 	assertThrown(parseSDL(rec, sdl, null, "testfile"));
550 }
551 
552 unittest { // test single value fields
553 	import std.exception;
554 	PackageRecipe rec;
555 	assertThrown!Exception(parseSDL(rec, `name "hello" "world"`, null, "testfile"));
556 	assertThrown!Exception(parseSDL(rec, `name`, null, "testfile"));
557 	assertThrown!Exception(parseSDL(rec, `name 10`, null, "testfile"));
558 	assertThrown!Exception(parseSDL(rec,
559 		`name "hello" {
560 			world
561 		}`, null, "testfile"));
562 	assertThrown!Exception(parseSDL(rec,
563 		`name ""
564 		versions "hello" 10`
565 		, null, "testfile"));
566 }
567 
568 unittest { // test basic serialization
569 	PackageRecipe p;
570 	p.name = "test";
571 	p.authors = ["foo", "bar"];
572 	p.buildSettings.dflags["-windows"] = ["-a"];
573 	p.buildSettings.lflags[""] = ["-b", "-c"];
574 	auto sdl = toSDL(p).toSDLDocument();
575 	assert(sdl ==
576 `name "test"
577 authors "foo" "bar"
578 dflags "-a" platform="windows"
579 lflags "-b" "-c"
580 `);
581 }
582 
583 unittest {
584 	auto sdl = "name \"test\"\nsourcePaths";
585 	PackageRecipe rec;
586 	parseSDL(rec, sdl, null, "testfile");
587 	assert("" in rec.buildSettings.sourcePaths);
588 }