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.dyaml.stdsumtype;
13 import dub.internal.logging;
14 import dub.internal.sdlang;
15 import dub.internal.vibecompat.inet.path;
16 import dub.recipe.packagerecipe;
17 
18 import std.algorithm : map;
19 import std.array : array;
20 import std.conv;
21 import std.string : startsWith, format;
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 	// parse configurations
68 	recipe.configurations.length = configs.length;
69 	foreach (i, n; configs) {
70 		parseConfiguration(n, recipe.configurations[i], full_name);
71 	}
72 
73 	// finally parse all sub packages
74 	recipe.subPackages.length = subpacks.length;
75 	foreach (i, n; subpacks) {
76 		if (n.values.length) {
77 			recipe.subPackages[i].path = n.stringTagValue;
78 		} else {
79 			enforceSDL(n.attributes.length == 0, "No attributes allowed for inline sub package definitions.", n);
80 			parseSDL(recipe.subPackages[i].recipe, n, full_name);
81 		}
82 	}
83 }
84 
85 Tag toSDL(const scope ref PackageRecipe recipe)
86 {
87 	Tag ret = new Tag;
88 	void add(T)(string field, T value) { ret.add(new Tag(null, field, [Value(value)])); }
89 	add("name", recipe.name);
90 	if (recipe.version_.length) add("version", recipe.version_);
91 	if (recipe.description.length) add("description", recipe.description);
92 	if (recipe.homepage.length) add("homepage", recipe.homepage);
93 	if (recipe.authors.length) ret.add(new Tag(null, "authors", recipe.authors.map!(a => Value(a)).array));
94 	if (recipe.copyright.length) add("copyright", recipe.copyright);
95 	if (recipe.license.length) add("license", recipe.license);
96 	foreach (name, settings; recipe.buildTypes) {
97 		auto t = new Tag(null, "buildType", [Value(name)]);
98 		t.add(settings.toSDL());
99 		ret.add(t);
100 	}
101 	if (!recipe.toolchainRequirements.empty) {
102 		ret.add(toSDL(recipe.toolchainRequirements));
103 	}
104 	if (recipe.ddoxFilterArgs.length)
105 		ret.add(new Tag("x", "ddoxFilterArgs", recipe.ddoxFilterArgs.map!(a => Value(a)).array));
106 	if (recipe.ddoxTool.length) ret.add(new Tag("x", "ddoxTool", [Value(recipe.ddoxTool)]));
107 	ret.add(recipe.buildSettings.toSDL());
108 	foreach(config; recipe.configurations)
109 		ret.add(config.toSDL());
110 	foreach (i, subPackage; recipe.subPackages) {
111 		if (subPackage.path !is null) {
112 			add("subPackage", subPackage.path);
113 		} else {
114 			auto t = subPackage.recipe.toSDL();
115 			t.name = "subPackage";
116 			ret.add(t);
117 		}
118 	}
119 	return ret;
120 }
121 
122 private void parseBuildSettings(Tag settings, ref BuildSettingsTemplate bs, string package_name)
123 {
124 	foreach (setting; settings.all.tags)
125 		parseBuildSetting(setting, bs, package_name);
126 }
127 
128 private void parseBuildSetting(Tag setting, ref BuildSettingsTemplate bs, string package_name)
129 {
130 	switch (setting.fullName) {
131 		default: break;
132 		case "dependency": parseDependency(setting, bs, package_name); break;
133 		case "systemDependencies": bs.systemDependencies = setting.stringTagValue; break;
134 		case "targetType": bs.targetType = setting.stringTagValue.to!TargetType; break;
135 		case "targetName": bs.targetName = setting.stringTagValue; break;
136 		case "targetPath": bs.targetPath = setting.stringTagValue; break;
137 		case "workingDirectory": bs.workingDirectory = setting.stringTagValue; break;
138 		case "subConfiguration":
139 			auto args = setting.stringArrayTagValue;
140 			enforceSDL(args.length == 2, "Expecting package and configuration names as arguments.", setting);
141 			bs.subConfigurations[expandPackageName(args[0], package_name, setting)] = args[1];
142 			break;
143 		case "dflags": setting.parsePlatformStringArray(bs.dflags); break;
144 		case "lflags": setting.parsePlatformStringArray(bs.lflags); break;
145 		case "libs": setting.parsePlatformStringArray(bs.libs); break;
146 		case "sourceFiles": setting.parsePlatformStringArray(bs.sourceFiles); break;
147 		case "sourcePaths": setting.parsePlatformStringArray(bs.sourcePaths); break;
148 		case "excludedSourceFiles": setting.parsePlatformStringArray(bs.excludedSourceFiles); break;
149 		case "mainSourceFile": bs.mainSourceFile = setting.stringTagValue; break;
150 		case "injectSourceFiles": setting.parsePlatformStringArray(bs.injectSourceFiles); break;
151 		case "copyFiles": setting.parsePlatformStringArray(bs.copyFiles); break;
152 		case "extraDependencyFiles": setting.parsePlatformStringArray(bs.extraDependencyFiles); break;
153 		case "versions": setting.parsePlatformStringArray(bs.versions); break;
154 		case "debugVersions": setting.parsePlatformStringArray(bs.debugVersions); break;
155 		case "x:versionFilters": setting.parsePlatformStringArray(bs.versionFilters); break;
156 		case "x:debugVersionFilters": setting.parsePlatformStringArray(bs.debugVersionFilters); break;
157 		case "importPaths": setting.parsePlatformStringArray(bs.importPaths); break;
158 		case "stringImportPaths": setting.parsePlatformStringArray(bs.stringImportPaths); break;
159 		case "preGenerateCommands": setting.parsePlatformStringArray(bs.preGenerateCommands); break;
160 		case "postGenerateCommands": setting.parsePlatformStringArray(bs.postGenerateCommands); break;
161 		case "preBuildCommands": setting.parsePlatformStringArray(bs.preBuildCommands); break;
162 		case "postBuildCommands": setting.parsePlatformStringArray(bs.postBuildCommands); break;
163 		case "preRunCommands": setting.parsePlatformStringArray(bs.preRunCommands); break;
164 		case "postRunCommands": setting.parsePlatformStringArray(bs.postRunCommands); break;
165 		case "environments": setting.parsePlatformStringAA(bs.environments); break;
166 		case "buildEnvironments": setting.parsePlatformStringAA(bs.buildEnvironments); break;
167 		case "runEnvironments": setting.parsePlatformStringAA(bs.runEnvironments); break;
168 		case "preGenerateEnvironments": setting.parsePlatformStringAA(bs.preGenerateEnvironments); break;
169 		case "postGenerateEnvironments": setting.parsePlatformStringAA(bs.postGenerateEnvironments); break;
170 		case "preBuildEnvironments": setting.parsePlatformStringAA(bs.preBuildEnvironments); break;
171 		case "postBuildEnvironments": setting.parsePlatformStringAA(bs.postBuildEnvironments); break;
172 		case "preRunEnvironments": setting.parsePlatformStringAA(bs.preRunEnvironments); break;
173 		case "postRunEnvironments": setting.parsePlatformStringAA(bs.postRunEnvironments); break;
174 		case "buildRequirements": setting.parsePlatformEnumArray!BuildRequirement(bs.buildRequirements); break;
175 		case "buildOptions": setting.parsePlatformEnumArray!BuildOption(bs.buildOptions); break;
176 	}
177 }
178 
179 private void parseDependency(Tag t, ref BuildSettingsTemplate bs, string package_name)
180 {
181 	enforceSDL(t.values.length != 0, "Missing dependency name.", t);
182 	enforceSDL(t.values.length == 1, "Multiple dependency names.", t);
183 	auto pkg = expandPackageName(t.values[0].expect!string(t), package_name, t);
184 	enforceSDL(pkg !in bs.dependencies, "The dependency '"~pkg~"' is specified more than once.", t);
185 
186 	Dependency dep = Dependency.any;
187 	auto attrs = t.attributes;
188 
189 	if ("path" in attrs) {
190 		dep = Dependency(NativePath(attrs["path"][0].value.expect!string(t, t.fullName ~ " path")));
191 	} else if ("repository" in attrs) {
192 		enforceSDL("version" in attrs, "Missing version specification.", t);
193 
194 		dep = Dependency(Repository(attrs["repository"][0].value.expect!string(t, t.fullName ~ " repository"),
195                                     attrs["version"][0].value.expect!string(t, t.fullName ~ " version")));
196 	} else {
197 		enforceSDL("version" in attrs, "Missing version specification.", t);
198 		dep = Dependency(attrs["version"][0].value.expect!string(t, t.fullName ~ " version"));
199 	}
200 
201 	if ("optional" in attrs)
202 		dep.optional = attrs["optional"][0].value.expect!bool(t, t.fullName ~ " optional");
203 
204 	if ("default" in attrs)
205 		dep.default_ = attrs["default"][0].value.expect!bool(t, t.fullName ~ " default");
206 
207 	bs.dependencies[pkg] = dep;
208 
209 	BuildSettingsTemplate dbs;
210 	parseBuildSettings(t, bs.dependencies[pkg].settings, package_name);
211 }
212 
213 private void parseConfiguration(Tag t, ref ConfigurationInfo ret, string package_name)
214 {
215 	ret.name = t.stringTagValue(true);
216 	foreach (f; t.tags) {
217 		switch (f.fullName) {
218 			default: parseBuildSetting(f, ret.buildSettings, package_name); break;
219 			case "platforms": ret.platforms ~= f.stringArrayTagValue; break;
220 		}
221 	}
222 }
223 
224 private Tag toSDL(const scope ref ConfigurationInfo config)
225 {
226 	auto ret = new Tag(null, "configuration", [Value(config.name)]);
227 	if (config.platforms.length) ret.add(new Tag(null, "platforms", config.platforms[].map!(p => Value(p)).array));
228 	ret.add(config.buildSettings.toSDL());
229 	return ret;
230 }
231 
232 private Tag[] toSDL(const scope ref BuildSettingsTemplate bs)
233 {
234 	Tag[] ret;
235 	void add(string name, string value, string namespace = null) { ret ~= new Tag(namespace, name, [Value(value)]); }
236 	void adda(string name, string suffix, in string[] values, string namespace = null) {
237 		ret ~= new Tag(namespace, name, values[].map!(v => Value(v)).array,
238 			suffix.length ? [new Attribute(null, "platform", Value(suffix))] : null);
239 	}
240 	void addaa(string name, string suffix, in string[string] values, string namespace = null) {
241 		foreach (k, v; values) {
242 			ret ~= new Tag(namespace, name, [Value(k), Value(v)],
243 				suffix.length ? [new Attribute(null, "platform", Value(suffix))] : null);
244 		}
245 	}
246 
247 	string[] toNameArray(T, U)(U bits) if(is(T == enum)) {
248 		string[] ret;
249 		foreach (m; __traits(allMembers, T))
250 			if (bits & __traits(getMember, T, m))
251 				ret ~= m;
252 		return ret;
253 	}
254 
255 	foreach (pack, d; bs.dependencies) {
256 		Attribute[] attribs;
257 		d.visit!(
258 			(const Repository	r) {
259 				attribs ~= new Attribute(null, "repository", Value(r.toString()));
260 				attribs ~= new Attribute(null, "version", Value(r.ref_));
261 			},
262 			(const NativePath	p) {
263 				attribs ~= new Attribute(null, "path", Value(p.toString()));
264 			},
265 			(const VersionRange v) {
266 				attribs ~= new Attribute(null, "version", Value(v.toString()));
267 			},
268 		);
269 		if (d.optional) attribs ~= new Attribute(null, "optional", Value(true));
270 		auto t = new Tag(null, "dependency", [Value(pack)], attribs);
271 		if (d.settings !is typeof(d.settings).init)
272 			t.add(d.settings.toSDL());
273 		ret ~= t;
274 	}
275 	if (bs.systemDependencies !is null) add("systemDependencies", bs.systemDependencies);
276 	if (bs.targetType != TargetType.autodetect) add("targetType", bs.targetType.to!string());
277 	if (bs.targetPath.length) add("targetPath", bs.targetPath);
278 	if (bs.targetName.length) add("targetName", bs.targetName);
279 	if (bs.workingDirectory.length) add("workingDirectory", bs.workingDirectory);
280 	if (bs.mainSourceFile.length) add("mainSourceFile", bs.mainSourceFile);
281 	foreach (pack, conf; bs.subConfigurations) ret ~= new Tag(null, "subConfiguration", [Value(pack), Value(conf)]);
282 	foreach (suffix, arr; bs.dflags) adda("dflags", suffix, arr);
283 	foreach (suffix, arr; bs.lflags) adda("lflags", suffix, arr);
284 	foreach (suffix, arr; bs.libs) adda("libs", suffix, arr);
285 	foreach (suffix, arr; bs.sourceFiles) adda("sourceFiles", suffix, arr);
286 	foreach (suffix, arr; bs.sourcePaths) adda("sourcePaths", suffix, arr);
287 	foreach (suffix, arr; bs.excludedSourceFiles) adda("excludedSourceFiles", suffix, arr);
288 	foreach (suffix, arr; bs.injectSourceFiles) adda("injectSourceFiles", suffix, arr);
289 	foreach (suffix, arr; bs.copyFiles) adda("copyFiles", suffix, arr);
290 	foreach (suffix, arr; bs.extraDependencyFiles) adda("extraDependencyFiles", suffix, arr);
291 	foreach (suffix, arr; bs.versions) adda("versions", suffix, arr);
292 	foreach (suffix, arr; bs.debugVersions) adda("debugVersions", suffix, arr);
293 	foreach (suffix, arr; bs.versionFilters) adda("versionFilters", suffix, arr, "x");
294 	foreach (suffix, arr; bs.debugVersionFilters) adda("debugVersionFilters", suffix, arr, "x");
295 	foreach (suffix, arr; bs.importPaths) adda("importPaths", suffix, arr);
296 	foreach (suffix, arr; bs.stringImportPaths) adda("stringImportPaths", suffix, arr);
297 	foreach (suffix, arr; bs.preGenerateCommands) adda("preGenerateCommands", suffix, arr);
298 	foreach (suffix, arr; bs.postGenerateCommands) adda("postGenerateCommands", suffix, arr);
299 	foreach (suffix, arr; bs.preBuildCommands) adda("preBuildCommands", suffix, arr);
300 	foreach (suffix, arr; bs.postBuildCommands) adda("postBuildCommands", suffix, arr);
301 	foreach (suffix, arr; bs.preRunCommands) adda("preRunCommands", suffix, arr);
302 	foreach (suffix, arr; bs.postRunCommands) adda("postRunCommands", suffix, arr);
303 	foreach (suffix, aa; bs.environments) addaa("environments", suffix, aa);
304 	foreach (suffix, aa; bs.buildEnvironments) addaa("buildEnvironments", suffix, aa);
305 	foreach (suffix, aa; bs.runEnvironments) addaa("runEnvironments", suffix, aa);
306 	foreach (suffix, aa; bs.preGenerateEnvironments) addaa("preGenerateEnvironments", suffix, aa);
307 	foreach (suffix, aa; bs.postGenerateEnvironments) addaa("postGenerateEnvironments", suffix, aa);
308 	foreach (suffix, aa; bs.preBuildEnvironments) addaa("preBuildEnvironments", suffix, aa);
309 	foreach (suffix, aa; bs.postBuildEnvironments) addaa("postBuildEnvironments", suffix, aa);
310 	foreach (suffix, aa; bs.preRunEnvironments) addaa("preRunEnvironments", suffix, aa);
311 	foreach (suffix, aa; bs.postRunEnvironments) addaa("postRunEnvironments", suffix, aa);
312 	foreach (suffix, bits; bs.buildRequirements) adda("buildRequirements", suffix, toNameArray!BuildRequirement(bits));
313 	foreach (suffix, bits; bs.buildOptions) adda("buildOptions", suffix, toNameArray!BuildOption(bits));
314 	return ret;
315 }
316 
317 private void parseToolchainRequirements(ref ToolchainRequirements tr, Tag tag)
318 {
319 	foreach (attr; tag.attributes)
320 		tr.addRequirement(attr.name, attr.value.expect!string(tag));
321 }
322 
323 private Tag toSDL(const ref ToolchainRequirements tr)
324 {
325 	Attribute[] attrs;
326 	if (tr.dub != Dependency.any) attrs ~= new Attribute("dub", Value(tr.dub.toString()));
327 	if (tr.frontend != Dependency.any) attrs ~= new Attribute("frontend", Value(tr.frontend.toString()));
328 	if (tr.dmd != Dependency.any) attrs ~= new Attribute("dmd", Value(tr.dmd.toString()));
329 	if (tr.ldc != Dependency.any) attrs ~= new Attribute("ldc", Value(tr.ldc.toString()));
330 	if (tr.gdc != Dependency.any) attrs ~= new Attribute("gdc", Value(tr.gdc.toString()));
331 	return new Tag(null, "toolchainRequirements", null, attrs);
332 }
333 
334 private string expandPackageName(string name, string parent_name, Tag tag)
335 {
336 	import std.algorithm : canFind;
337 	if (name.startsWith(":")) {
338 		enforceSDL(!parent_name.canFind(':'), format("Short-hand packages syntax not allowed within sub packages: %s -> %s", parent_name, name), tag);
339 		return parent_name ~ name;
340 	} else return name;
341 }
342 
343 private string stringTagValue(Tag t, bool allow_child_tags = false)
344 {
345 	enforceSDL(t.values.length > 0, format("Missing string value for '%s'.", t.fullName), t);
346 	enforceSDL(t.values.length == 1, format("Expected only one value for '%s'.", t.fullName), t);
347 	enforceSDL(allow_child_tags || t.tags.length == 0, format("No child tags allowed for '%s'.", t.fullName), t);
348 	// Q: should attributes be disallowed, or just ignored for forward compatibility reasons?
349 	//enforceSDL(t.attributes.length == 0, format("No attributes allowed for '%s'.", t.fullName), t);
350 	return t.values[0].expect!string(t);
351 }
352 
353 private T expect(T)(
354 	Value value,
355 	Tag errorInfo,
356 	string customFieldName = null,
357 	string file = __FILE__,
358 	int line = __LINE__
359 )
360 {
361 	return value.match!(
362 		(T v) => v,
363 		(fallback)
364 		{
365 			enforceSDL(false, format("Expected value of type " ~ T.stringof ~ " for '%s', but got %s.",
366 				customFieldName.length ? customFieldName : errorInfo.fullName,
367 				typeof(fallback).stringof),
368 				errorInfo, file, line);
369 			return T.init;
370 		}
371 	);
372 }
373 
374 private string[] stringArrayTagValue(Tag t, bool allow_child_tags = false)
375 {
376 	enforceSDL(allow_child_tags || t.tags.length == 0, format("No child tags allowed for '%s'.", t.fullName), t);
377 	// Q: should attributes be disallowed, or just ignored for forward compatibility reasons?
378 	//enforceSDL(t.attributes.length == 0, format("No attributes allowed for '%s'.", t.fullName), t);
379 
380 	string[] ret;
381 	foreach (i, v; t.values) {
382 		ret ~= v.expect!string(t, text(t.fullName, "[", i, "]"));
383 	}
384 	return ret;
385 }
386 
387 private string getPlatformSuffix(Tag t, string file = __FILE__, int line = __LINE__)
388 {
389 	string platform;
390 	if ("platform" in t.attributes)
391 		platform = t.attributes["platform"][0].value.expect!string(t, t.fullName ~ " platform", file, line);
392 	return platform;
393 }
394 
395 private void parsePlatformStringArray(Tag t, ref string[][string] dst)
396 {
397 	string platform = t.getPlatformSuffix;
398 	dst[platform] ~= t.values.map!(v => v.expect!string(t)).array;
399 }
400 private void parsePlatformStringAA(Tag t, ref string[string][string] dst)
401 {
402 	string platform = t.getPlatformSuffix;
403 	enforceSDL(t.values.length == 2, format("Values for '%s' must be 2 required.", t.fullName), t);
404 	dst[platform][t.values[0].expect!string(t)] = t.values[1].expect!string(t);
405 }
406 
407 private void parsePlatformEnumArray(E, Es)(Tag t, ref Es[string] dst)
408 {
409 	string platform = t.getPlatformSuffix;
410 	foreach (v; t.values) {
411 		if (platform !in dst) dst[platform] = Es.init;
412 		dst[platform] |= v.expect!string(t).to!E;
413 	}
414 }
415 
416 private void enforceSDL(bool condition, lazy string message, Tag tag, string file = __FILE__, int line = __LINE__)
417 {
418 	if (!condition) {
419 		throw new Exception(format("%s(%s): Error: %s", tag.location.file, tag.location.line + 1, message), file, line);
420 	}
421 }
422 
423 
424 unittest { // test all possible fields
425 	auto sdl =
426 `name "projectname";
427 description "project description";
428 homepage "http://example.com"
429 authors "author 1" "author 2"
430 authors "author 3"
431 copyright "copyright string"
432 license "license string"
433 version "1.0.0"
434 subPackage {
435 	name "subpackage1"
436 }
437 subPackage {
438 	name "subpackage2"
439 	dependency "projectname:subpackage1" version="*"
440 }
441 subPackage "pathsp3"
442 configuration "config1" {
443 	platforms "windows" "linux"
444 	targetType "library"
445 }
446 configuration "config2" {
447 	platforms "windows-x86"
448 	targetType "executable"
449 }
450 buildType "debug" {
451 	dflags "-g" "-debug"
452 }
453 buildType "release" {
454 	dflags "-release" "-O"
455 }
456 toolchainRequirements dub="~>1.11.0" dmd="~>2.082"
457 x:ddoxFilterArgs "-arg1" "-arg2"
458 x:ddoxFilterArgs "-arg3"
459 x:ddoxTool "ddoxtool"
460 
461 dependency ":subpackage1" optional=false path="." {
462 	dflags "-g" "-debug"
463 }
464 dependency "somedep" version="1.0.0" optional=true
465 systemDependencies "system dependencies"
466 targetType "executable"
467 targetName "target name"
468 targetPath "target path"
469 workingDirectory "working directory"
470 subConfiguration ":subpackage2" "library"
471 buildRequirements "allowWarnings" "silenceDeprecations"
472 buildOptions "verbose" "ignoreUnknownPragmas"
473 libs "lib1" "lib2"
474 libs "lib3"
475 sourceFiles "source1" "source2"
476 sourceFiles "source3"
477 sourcePaths "sourcepath1" "sourcepath2"
478 sourcePaths "sourcepath3"
479 excludedSourceFiles "excluded1" "excluded2"
480 excludedSourceFiles "excluded3"
481 mainSourceFile "main source"
482 injectSourceFiles "finalbinarysourcefile.d" "extrafile"
483 copyFiles "copy1" "copy2"
484 copyFiles "copy3"
485 extraDependencyFiles "extradepfile1" "extradepfile2"
486 extraDependencyFiles "extradepfile3"
487 versions "version1" "version2"
488 versions "version3"
489 debugVersions "debug1" "debug2"
490 debugVersions "debug3"
491 x:versionFilters "version1" "version2"
492 x:versionFilters "version3"
493 x:versionFilters
494 x:debugVersionFilters "debug1" "debug2"
495 x:debugVersionFilters "debug3"
496 x:debugVersionFilters
497 importPaths "import1" "import2"
498 importPaths "import3"
499 stringImportPaths "string1" "string2"
500 stringImportPaths "string3"
501 preGenerateCommands "preg1" "preg2"
502 preGenerateCommands "preg3"
503 postGenerateCommands "postg1" "postg2"
504 postGenerateCommands "postg3"
505 preBuildCommands "preb1" "preb2"
506 preBuildCommands "preb3"
507 postBuildCommands "postb1" "postb2"
508 postBuildCommands "postb3"
509 preRunCommands "prer1" "prer2"
510 preRunCommands "prer3"
511 postRunCommands "postr1" "postr2"
512 postRunCommands "postr3"
513 environments "Var1" "env"
514 buildEnvironments "Var2" "buildEnv"
515 runEnvironments "Var3" "runEnv"
516 preGenerateEnvironments "Var4" "preGenEnv"
517 postGenerateEnvironments "Var5" "postGenEnv"
518 preBuildEnvironments "Var6" "preBuildEnv"
519 postBuildEnvironments "Var7" "postBuildEnv"
520 preRunEnvironments "Var8" "preRunEnv"
521 postRunEnvironments "Var9" "postRunEnv"
522 dflags "df1" "df2"
523 dflags "df3"
524 lflags "lf1" "lf2"
525 lflags "lf3"
526 `;
527 	PackageRecipe rec1;
528 	parseSDL(rec1, sdl, null, "testfile");
529 	PackageRecipe rec;
530 	parseSDL(rec, rec1.toSDL(), null); // verify that all fields are serialized properly
531 
532 	assert(rec.name == "projectname");
533 	assert(rec.description == "project description");
534 	assert(rec.homepage == "http://example.com");
535 	assert(rec.authors == ["author 1", "author 2", "author 3"]);
536 	assert(rec.copyright == "copyright string");
537 	assert(rec.license == "license string");
538 	assert(rec.version_ == "1.0.0");
539 	assert(rec.subPackages.length == 3);
540 	assert(rec.subPackages[0].path == "");
541 	assert(rec.subPackages[0].recipe.name == "subpackage1");
542 	assert(rec.subPackages[1].path == "");
543 	assert(rec.subPackages[1].recipe.name == "subpackage2");
544 	assert(rec.subPackages[1].recipe.buildSettings.dependencies.length == 1);
545 	assert("projectname:subpackage1" in rec.subPackages[1].recipe.buildSettings.dependencies);
546 	assert(rec.subPackages[2].path == "pathsp3");
547 	assert(rec.configurations.length == 2);
548 	assert(rec.configurations[0].name == "config1");
549 	assert(rec.configurations[0].platforms == ["windows", "linux"]);
550 	assert(rec.configurations[0].buildSettings.targetType == TargetType.library);
551 	assert(rec.configurations[1].name == "config2");
552 	assert(rec.configurations[1].platforms == ["windows-x86"]);
553 	assert(rec.configurations[1].buildSettings.targetType == TargetType.executable);
554 	assert(rec.buildTypes.length == 2);
555 	assert(rec.buildTypes["debug"].dflags == ["": ["-g", "-debug"]]);
556 	assert(rec.buildTypes["release"].dflags == ["": ["-release", "-O"]]);
557 	assert(rec.toolchainRequirements.dub == Dependency("~>1.11.0"));
558 	assert(rec.toolchainRequirements.frontend == Dependency.any);
559 	assert(rec.toolchainRequirements.dmd == Dependency("~>2.82.0"));
560 	assert(rec.toolchainRequirements.ldc == Dependency.any);
561 	assert(rec.toolchainRequirements.gdc == Dependency.any);
562 	assert(rec.ddoxFilterArgs == ["-arg1", "-arg2", "-arg3"], rec.ddoxFilterArgs.to!string);
563 	assert(rec.ddoxTool == "ddoxtool");
564 	assert(rec.buildSettings.dependencies.length == 2);
565 	assert(rec.buildSettings.dependencies["projectname:subpackage1"].optional == false);
566 	assert(rec.buildSettings.dependencies["projectname:subpackage1"].path == NativePath("."));
567 	assert(rec.buildSettings.dependencies["projectname:subpackage1"].settings.dflags == ["":["-g", "-debug"]]);
568 	assert(rec.buildSettings.dependencies["somedep"].version_.toString() == "1.0.0");
569 	assert(rec.buildSettings.dependencies["somedep"].optional == true);
570 	assert(rec.buildSettings.systemDependencies == "system dependencies");
571 	assert(rec.buildSettings.targetType == TargetType.executable);
572 	assert(rec.buildSettings.targetName == "target name");
573 	assert(rec.buildSettings.targetPath == "target path");
574 	assert(rec.buildSettings.workingDirectory == "working directory");
575 	assert(rec.buildSettings.subConfigurations.length == 1);
576 	assert(rec.buildSettings.subConfigurations["projectname:subpackage2"] == "library");
577 	assert(rec.buildSettings.buildRequirements == ["": cast(Flags!BuildRequirement)(BuildRequirement.allowWarnings | BuildRequirement.silenceDeprecations)]);
578 	assert(rec.buildSettings.buildOptions == ["": cast(Flags!BuildOption)(BuildOption.verbose | BuildOption.ignoreUnknownPragmas)]);
579 	assert(rec.buildSettings.libs == ["": ["lib1", "lib2", "lib3"]]);
580 	assert(rec.buildSettings.sourceFiles == ["": ["source1", "source2", "source3"]]);
581 	assert(rec.buildSettings.sourcePaths == ["": ["sourcepath1", "sourcepath2", "sourcepath3"]]);
582 	assert(rec.buildSettings.excludedSourceFiles == ["": ["excluded1", "excluded2", "excluded3"]]);
583 	assert(rec.buildSettings.mainSourceFile == "main source");
584 	assert(rec.buildSettings.sourceFiles == ["": ["source1", "source2", "source3"]]);
585 	assert(rec.buildSettings.injectSourceFiles == ["": ["finalbinarysourcefile.d", "extrafile"]]);
586 	assert(rec.buildSettings.extraDependencyFiles == ["": ["extradepfile1", "extradepfile2", "extradepfile3"]]);
587 	assert(rec.buildSettings.versions == ["": ["version1", "version2", "version3"]]);
588 	assert(rec.buildSettings.debugVersions == ["": ["debug1", "debug2", "debug3"]]);
589 	assert(rec.buildSettings.versionFilters == ["": ["version1", "version2", "version3"]]);
590 	assert(rec.buildSettings.debugVersionFilters == ["": ["debug1", "debug2", "debug3"]]);
591 	assert(rec.buildSettings.importPaths == ["": ["import1", "import2", "import3"]]);
592 	assert(rec.buildSettings.stringImportPaths == ["": ["string1", "string2", "string3"]]);
593 	assert(rec.buildSettings.preGenerateCommands == ["": ["preg1", "preg2", "preg3"]]);
594 	assert(rec.buildSettings.postGenerateCommands == ["": ["postg1", "postg2", "postg3"]]);
595 	assert(rec.buildSettings.preBuildCommands == ["": ["preb1", "preb2", "preb3"]]);
596 	assert(rec.buildSettings.postBuildCommands == ["": ["postb1", "postb2", "postb3"]]);
597 	assert(rec.buildSettings.preRunCommands == ["": ["prer1", "prer2", "prer3"]]);
598 	assert(rec.buildSettings.postRunCommands == ["": ["postr1", "postr2", "postr3"]]);
599 	assert(rec.buildSettings.environments == ["": ["Var1": "env"]]);
600 	assert(rec.buildSettings.buildEnvironments == ["": ["Var2": "buildEnv"]]);
601 	assert(rec.buildSettings.runEnvironments == ["": ["Var3": "runEnv"]]);
602 	assert(rec.buildSettings.preGenerateEnvironments == ["": ["Var4": "preGenEnv"]]);
603 	assert(rec.buildSettings.postGenerateEnvironments == ["": ["Var5": "postGenEnv"]]);
604 	assert(rec.buildSettings.preBuildEnvironments == ["": ["Var6": "preBuildEnv"]]);
605 	assert(rec.buildSettings.postBuildEnvironments == ["": ["Var7": "postBuildEnv"]]);
606 	assert(rec.buildSettings.preRunEnvironments == ["": ["Var8": "preRunEnv"]]);
607 	assert(rec.buildSettings.postRunEnvironments == ["": ["Var9": "postRunEnv"]]);
608 	assert(rec.buildSettings.dflags == ["": ["df1", "df2", "df3"]]);
609 	assert(rec.buildSettings.lflags == ["": ["lf1", "lf2", "lf3"]]);
610 }
611 
612 unittest { // test platform identifiers
613 	auto sdl =
614 `name "testproject"
615 dflags "-a" "-b" platform="windows-x86"
616 dflags "-c" platform="windows-x86"
617 dflags "-e" "-f"
618 dflags "-g"
619 dflags "-h" "-i" platform="linux"
620 dflags "-j" platform="linux"
621 `;
622 	PackageRecipe rec;
623 	parseSDL(rec, sdl, null, "testfile");
624 	assert(rec.buildSettings.dflags.length == 3);
625 	assert(rec.buildSettings.dflags["windows-x86"] == ["-a", "-b", "-c"]);
626 	assert(rec.buildSettings.dflags[""] == ["-e", "-f", "-g"]);
627 	assert(rec.buildSettings.dflags["linux"] == ["-h", "-i", "-j"]);
628 }
629 
630 unittest { // test for missing name field
631 	import std.exception;
632 	auto sdl = `description "missing name"`;
633 	PackageRecipe rec;
634 	assertThrown(parseSDL(rec, sdl, null, "testfile"));
635 }
636 
637 unittest { // test single value fields
638 	import std.exception;
639 	PackageRecipe rec;
640 	assertThrown!Exception(parseSDL(rec, `name "hello" "world"`, null, "testfile"));
641 	assertThrown!Exception(parseSDL(rec, `name`, null, "testfile"));
642 	assertThrown!Exception(parseSDL(rec, `name 10`, null, "testfile"));
643 	assertThrown!Exception(parseSDL(rec,
644 		`name "hello" {
645 			world
646 		}`, null, "testfile"));
647 	assertThrown!Exception(parseSDL(rec,
648 		`name ""
649 		versions "hello" 10`
650 		, null, "testfile"));
651 }
652 
653 unittest { // test basic serialization
654 	PackageRecipe p;
655 	p.name = "test";
656 	p.authors = ["foo", "bar"];
657 	p.buildSettings.dflags["windows"] = ["-a"];
658 	p.buildSettings.lflags[""] = ["-b", "-c"];
659 	auto sdl = toSDL(p).toSDLDocument();
660 	assert(sdl ==
661 `name "test"
662 authors "foo" "bar"
663 dflags "-a" platform="windows"
664 lflags "-b" "-c"
665 `);
666 }
667 
668 unittest {
669 	auto sdl = "name \"test\"\nsourcePaths";
670 	PackageRecipe rec;
671 	parseSDL(rec, sdl, null, "testfile");
672 	assert("" in rec.buildSettings.sourcePaths);
673 }
674 
675 unittest {
676 	auto sdl =
677 `name "test"
678 dependency "package" repository="git+https://some.url" version="12345678"
679 `;
680 	PackageRecipe rec;
681 	parseSDL(rec, sdl, null, "testfile");
682 	auto dependency = rec.buildSettings.dependencies["package"];
683 	assert(!dependency.repository.empty);
684 	assert(dependency.repository.ref_ == "12345678");
685 }
686 
687 unittest {
688 	PackageRecipe p;
689 	p.name = "test";
690 
691 	auto repository = Repository("git+https://some.url", "12345678");
692 	p.buildSettings.dependencies["package"] = Dependency(repository);
693 	auto sdl = toSDL(p).toSDLDocument();
694 	assert(sdl ==
695 `name "test"
696 dependency "package" repository="git+https://some.url" version="12345678"
697 `);
698 }