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.logging;
13 import dub.internal.sdlang;
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 	// 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].get!string, 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.get!string));
191 	} else if ("repository" in attrs) {
192 		enforceSDL("version" in attrs, "Missing version specification.", t);
193 
194 		dep = Dependency(Repository(attrs["repository"][0].value.get!string,
195                                     attrs["version"][0].value.get!string));
196 	} else {
197 		enforceSDL("version" in attrs, "Missing version specification.", t);
198 		dep = Dependency(attrs["version"][0].value.get!string);
199 	}
200 
201 	if ("optional" in attrs)
202 		dep.optional = attrs["optional"][0].value.get!bool;
203 
204 	if ("default" in attrs)
205 		dep.default_ = attrs["default"][0].value.get!bool;
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.get!string);
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 	import std.string : format;
338 	if (name.startsWith(":")) {
339 		enforceSDL(!parent_name.canFind(':'), format("Short-hand packages syntax not allowed within sub packages: %s -> %s", parent_name, name), tag);
340 		return parent_name ~ name;
341 	} else return name;
342 }
343 
344 private string stringTagValue(Tag t, bool allow_child_tags = false)
345 {
346 	import std.string : format;
347 	enforceSDL(t.values.length > 0, format("Missing string value for '%s'.", t.fullName), t);
348 	enforceSDL(t.values.length == 1, format("Expected only one value for '%s'.", t.fullName), t);
349 	enforceSDL(t.values[0].peek!string !is null, format("Expected value of type string for '%s'.", t.fullName), t);
350 	enforceSDL(allow_child_tags || t.tags.length == 0, format("No child tags allowed for '%s'.", t.fullName), t);
351 	// Q: should attributes be disallowed, or just ignored for forward compatibility reasons?
352 	//enforceSDL(t.attributes.length == 0, format("No attributes allowed for '%s'.", t.fullName), t);
353 	return t.values[0].get!string;
354 }
355 
356 private string[] stringArrayTagValue(Tag t, bool allow_child_tags = false)
357 {
358 	import std.string : format;
359 	enforceSDL(allow_child_tags || t.tags.length == 0, format("No child tags allowed for '%s'.", t.fullName), t);
360 	// Q: should attributes be disallowed, or just ignored for forward compatibility reasons?
361 	//enforceSDL(t.attributes.length == 0, format("No attributes allowed for '%s'.", t.fullName), t);
362 
363 	string[] ret;
364 	foreach (v; t.values) {
365 		enforceSDL(t.values[0].peek!string !is null, format("Values for '%s' must be strings.", t.fullName), t);
366 		ret ~= v.get!string;
367 	}
368 	return ret;
369 }
370 
371 private void parsePlatformStringArray(Tag t, ref string[][string] dst)
372 {
373 	string platform;
374 	if ("platform" in t.attributes)
375 		platform = t.attributes["platform"][0].value.get!string;
376 	dst[platform] ~= t.values.map!(v => v.get!string).array;
377 }
378 private void parsePlatformStringAA(Tag t, ref string[string][string] dst)
379 {
380 	import std.string : format;
381 	string platform;
382 	if ("platform" in t.attributes)
383 		platform = t.attributes["platform"][0].value.get!string;
384 	enforceSDL(t.values.length == 2, format("Values for '%s' must be 2 required.", t.fullName), t);
385 	enforceSDL(t.values[0].peek!string !is null, format("Values for '%s' must be strings.", t.fullName), t);
386 	enforceSDL(t.values[1].peek!string !is null, format("Values for '%s' must be strings.", t.fullName), t);
387 	dst[platform][t.values[0].get!string] = t.values[1].get!string;
388 }
389 
390 private void parsePlatformEnumArray(E, Es)(Tag t, ref Es[string] dst)
391 {
392 	string platform;
393 	if ("platform" in t.attributes)
394 		platform = t.attributes["platform"][0].value.get!string;
395 	foreach (v; t.values) {
396 		if (platform !in dst) dst[platform] = Es.init;
397 		dst[platform] |= v.get!string.to!E;
398 	}
399 }
400 
401 private void enforceSDL(bool condition, lazy string message, Tag tag, string file = __FILE__, int line = __LINE__)
402 {
403 	import std.string : format;
404 	if (!condition) {
405 		throw new Exception(format("%s(%s): Error: %s", tag.location.file, tag.location.line + 1, message), file, line);
406 	}
407 }
408 
409 
410 unittest { // test all possible fields
411 	auto sdl =
412 `name "projectname";
413 description "project description";
414 homepage "http://example.com"
415 authors "author 1" "author 2"
416 authors "author 3"
417 copyright "copyright string"
418 license "license string"
419 version "1.0.0"
420 subPackage {
421 	name "subpackage1"
422 }
423 subPackage {
424 	name "subpackage2"
425 	dependency "projectname:subpackage1" version="*"
426 }
427 subPackage "pathsp3"
428 configuration "config1" {
429 	platforms "windows" "linux"
430 	targetType "library"
431 }
432 configuration "config2" {
433 	platforms "windows-x86"
434 	targetType "executable"
435 }
436 buildType "debug" {
437 	dflags "-g" "-debug"
438 }
439 buildType "release" {
440 	dflags "-release" "-O"
441 }
442 toolchainRequirements dub="~>1.11.0" dmd="~>2.082"
443 x:ddoxFilterArgs "-arg1" "-arg2"
444 x:ddoxFilterArgs "-arg3"
445 x:ddoxTool "ddoxtool"
446 
447 dependency ":subpackage1" optional=false path="." {
448 	dflags "-g" "-debug"
449 }
450 dependency "somedep" version="1.0.0" optional=true
451 systemDependencies "system dependencies"
452 targetType "executable"
453 targetName "target name"
454 targetPath "target path"
455 workingDirectory "working directory"
456 subConfiguration ":subpackage2" "library"
457 buildRequirements "allowWarnings" "silenceDeprecations"
458 buildOptions "verbose" "ignoreUnknownPragmas"
459 libs "lib1" "lib2"
460 libs "lib3"
461 sourceFiles "source1" "source2"
462 sourceFiles "source3"
463 sourcePaths "sourcepath1" "sourcepath2"
464 sourcePaths "sourcepath3"
465 excludedSourceFiles "excluded1" "excluded2"
466 excludedSourceFiles "excluded3"
467 mainSourceFile "main source"
468 injectSourceFiles "finalbinarysourcefile.d" "extrafile"
469 copyFiles "copy1" "copy2"
470 copyFiles "copy3"
471 extraDependencyFiles "extradepfile1" "extradepfile2"
472 extraDependencyFiles "extradepfile3"
473 versions "version1" "version2"
474 versions "version3"
475 debugVersions "debug1" "debug2"
476 debugVersions "debug3"
477 x:versionFilters "version1" "version2"
478 x:versionFilters "version3"
479 x:versionFilters
480 x:debugVersionFilters "debug1" "debug2"
481 x:debugVersionFilters "debug3"
482 x:debugVersionFilters
483 importPaths "import1" "import2"
484 importPaths "import3"
485 stringImportPaths "string1" "string2"
486 stringImportPaths "string3"
487 preGenerateCommands "preg1" "preg2"
488 preGenerateCommands "preg3"
489 postGenerateCommands "postg1" "postg2"
490 postGenerateCommands "postg3"
491 preBuildCommands "preb1" "preb2"
492 preBuildCommands "preb3"
493 postBuildCommands "postb1" "postb2"
494 postBuildCommands "postb3"
495 preRunCommands "prer1" "prer2"
496 preRunCommands "prer3"
497 postRunCommands "postr1" "postr2"
498 postRunCommands "postr3"
499 environments "Var1" "env"
500 buildEnvironments "Var2" "buildEnv"
501 runEnvironments "Var3" "runEnv"
502 preGenerateEnvironments "Var4" "preGenEnv"
503 postGenerateEnvironments "Var5" "postGenEnv"
504 preBuildEnvironments "Var6" "preBuildEnv"
505 postBuildEnvironments "Var7" "postBuildEnv"
506 preRunEnvironments "Var8" "preRunEnv"
507 postRunEnvironments "Var9" "postRunEnv"
508 dflags "df1" "df2"
509 dflags "df3"
510 lflags "lf1" "lf2"
511 lflags "lf3"
512 `;
513 	PackageRecipe rec1;
514 	parseSDL(rec1, sdl, null, "testfile");
515 	PackageRecipe rec;
516 	parseSDL(rec, rec1.toSDL(), null); // verify that all fields are serialized properly
517 
518 	assert(rec.name == "projectname");
519 	assert(rec.description == "project description");
520 	assert(rec.homepage == "http://example.com");
521 	assert(rec.authors == ["author 1", "author 2", "author 3"]);
522 	assert(rec.copyright == "copyright string");
523 	assert(rec.license == "license string");
524 	assert(rec.version_ == "1.0.0");
525 	assert(rec.subPackages.length == 3);
526 	assert(rec.subPackages[0].path == "");
527 	assert(rec.subPackages[0].recipe.name == "subpackage1");
528 	assert(rec.subPackages[1].path == "");
529 	assert(rec.subPackages[1].recipe.name == "subpackage2");
530 	assert(rec.subPackages[1].recipe.buildSettings.dependencies.length == 1);
531 	assert("projectname:subpackage1" in rec.subPackages[1].recipe.buildSettings.dependencies);
532 	assert(rec.subPackages[2].path == "pathsp3");
533 	assert(rec.configurations.length == 2);
534 	assert(rec.configurations[0].name == "config1");
535 	assert(rec.configurations[0].platforms == ["windows", "linux"]);
536 	assert(rec.configurations[0].buildSettings.targetType == TargetType.library);
537 	assert(rec.configurations[1].name == "config2");
538 	assert(rec.configurations[1].platforms == ["windows-x86"]);
539 	assert(rec.configurations[1].buildSettings.targetType == TargetType.executable);
540 	assert(rec.buildTypes.length == 2);
541 	assert(rec.buildTypes["debug"].dflags == ["": ["-g", "-debug"]]);
542 	assert(rec.buildTypes["release"].dflags == ["": ["-release", "-O"]]);
543 	assert(rec.toolchainRequirements.dub == Dependency("~>1.11.0"));
544 	assert(rec.toolchainRequirements.frontend == Dependency.any);
545 	assert(rec.toolchainRequirements.dmd == Dependency("~>2.82.0"));
546 	assert(rec.toolchainRequirements.ldc == Dependency.any);
547 	assert(rec.toolchainRequirements.gdc == Dependency.any);
548 	assert(rec.ddoxFilterArgs == ["-arg1", "-arg2", "-arg3"], rec.ddoxFilterArgs.to!string);
549 	assert(rec.ddoxTool == "ddoxtool");
550 	assert(rec.buildSettings.dependencies.length == 2);
551 	assert(rec.buildSettings.dependencies["projectname:subpackage1"].optional == false);
552 	assert(rec.buildSettings.dependencies["projectname:subpackage1"].path == NativePath("."));
553 	assert(rec.buildSettings.dependencies["projectname:subpackage1"].settings.dflags == ["":["-g", "-debug"]]);
554 	assert(rec.buildSettings.dependencies["somedep"].version_.toString() == "1.0.0");
555 	assert(rec.buildSettings.dependencies["somedep"].optional == true);
556 	assert(rec.buildSettings.systemDependencies == "system dependencies");
557 	assert(rec.buildSettings.targetType == TargetType.executable);
558 	assert(rec.buildSettings.targetName == "target name");
559 	assert(rec.buildSettings.targetPath == "target path");
560 	assert(rec.buildSettings.workingDirectory == "working directory");
561 	assert(rec.buildSettings.subConfigurations.length == 1);
562 	assert(rec.buildSettings.subConfigurations["projectname:subpackage2"] == "library");
563 	assert(rec.buildSettings.buildRequirements == ["": cast(Flags!BuildRequirement)(BuildRequirement.allowWarnings | BuildRequirement.silenceDeprecations)]);
564 	assert(rec.buildSettings.buildOptions == ["": cast(Flags!BuildOption)(BuildOption.verbose | BuildOption.ignoreUnknownPragmas)]);
565 	assert(rec.buildSettings.libs == ["": ["lib1", "lib2", "lib3"]]);
566 	assert(rec.buildSettings.sourceFiles == ["": ["source1", "source2", "source3"]]);
567 	assert(rec.buildSettings.sourcePaths == ["": ["sourcepath1", "sourcepath2", "sourcepath3"]]);
568 	assert(rec.buildSettings.excludedSourceFiles == ["": ["excluded1", "excluded2", "excluded3"]]);
569 	assert(rec.buildSettings.mainSourceFile == "main source");
570 	assert(rec.buildSettings.sourceFiles == ["": ["source1", "source2", "source3"]]);
571 	assert(rec.buildSettings.injectSourceFiles == ["": ["finalbinarysourcefile.d", "extrafile"]]);
572 	assert(rec.buildSettings.extraDependencyFiles == ["": ["extradepfile1", "extradepfile2", "extradepfile3"]]);
573 	assert(rec.buildSettings.versions == ["": ["version1", "version2", "version3"]]);
574 	assert(rec.buildSettings.debugVersions == ["": ["debug1", "debug2", "debug3"]]);
575 	assert(rec.buildSettings.versionFilters == ["": ["version1", "version2", "version3"]]);
576 	assert(rec.buildSettings.debugVersionFilters == ["": ["debug1", "debug2", "debug3"]]);
577 	assert(rec.buildSettings.importPaths == ["": ["import1", "import2", "import3"]]);
578 	assert(rec.buildSettings.stringImportPaths == ["": ["string1", "string2", "string3"]]);
579 	assert(rec.buildSettings.preGenerateCommands == ["": ["preg1", "preg2", "preg3"]]);
580 	assert(rec.buildSettings.postGenerateCommands == ["": ["postg1", "postg2", "postg3"]]);
581 	assert(rec.buildSettings.preBuildCommands == ["": ["preb1", "preb2", "preb3"]]);
582 	assert(rec.buildSettings.postBuildCommands == ["": ["postb1", "postb2", "postb3"]]);
583 	assert(rec.buildSettings.preRunCommands == ["": ["prer1", "prer2", "prer3"]]);
584 	assert(rec.buildSettings.postRunCommands == ["": ["postr1", "postr2", "postr3"]]);
585 	assert(rec.buildSettings.environments == ["": ["Var1": "env"]]);
586 	assert(rec.buildSettings.buildEnvironments == ["": ["Var2": "buildEnv"]]);
587 	assert(rec.buildSettings.runEnvironments == ["": ["Var3": "runEnv"]]);
588 	assert(rec.buildSettings.preGenerateEnvironments == ["": ["Var4": "preGenEnv"]]);
589 	assert(rec.buildSettings.postGenerateEnvironments == ["": ["Var5": "postGenEnv"]]);
590 	assert(rec.buildSettings.preBuildEnvironments == ["": ["Var6": "preBuildEnv"]]);
591 	assert(rec.buildSettings.postBuildEnvironments == ["": ["Var7": "postBuildEnv"]]);
592 	assert(rec.buildSettings.preRunEnvironments == ["": ["Var8": "preRunEnv"]]);
593 	assert(rec.buildSettings.postRunEnvironments == ["": ["Var9": "postRunEnv"]]);
594 	assert(rec.buildSettings.dflags == ["": ["df1", "df2", "df3"]]);
595 	assert(rec.buildSettings.lflags == ["": ["lf1", "lf2", "lf3"]]);
596 }
597 
598 unittest { // test platform identifiers
599 	auto sdl =
600 `name "testproject"
601 dflags "-a" "-b" platform="windows-x86"
602 dflags "-c" platform="windows-x86"
603 dflags "-e" "-f"
604 dflags "-g"
605 dflags "-h" "-i" platform="linux"
606 dflags "-j" platform="linux"
607 `;
608 	PackageRecipe rec;
609 	parseSDL(rec, sdl, null, "testfile");
610 	assert(rec.buildSettings.dflags.length == 3);
611 	assert(rec.buildSettings.dflags["windows-x86"] == ["-a", "-b", "-c"]);
612 	assert(rec.buildSettings.dflags[""] == ["-e", "-f", "-g"]);
613 	assert(rec.buildSettings.dflags["linux"] == ["-h", "-i", "-j"]);
614 }
615 
616 unittest { // test for missing name field
617 	import std.exception;
618 	auto sdl = `description "missing name"`;
619 	PackageRecipe rec;
620 	assertThrown(parseSDL(rec, sdl, null, "testfile"));
621 }
622 
623 unittest { // test single value fields
624 	import std.exception;
625 	PackageRecipe rec;
626 	assertThrown!Exception(parseSDL(rec, `name "hello" "world"`, null, "testfile"));
627 	assertThrown!Exception(parseSDL(rec, `name`, null, "testfile"));
628 	assertThrown!Exception(parseSDL(rec, `name 10`, null, "testfile"));
629 	assertThrown!Exception(parseSDL(rec,
630 		`name "hello" {
631 			world
632 		}`, null, "testfile"));
633 	assertThrown!Exception(parseSDL(rec,
634 		`name ""
635 		versions "hello" 10`
636 		, null, "testfile"));
637 }
638 
639 unittest { // test basic serialization
640 	PackageRecipe p;
641 	p.name = "test";
642 	p.authors = ["foo", "bar"];
643 	p.buildSettings.dflags["windows"] = ["-a"];
644 	p.buildSettings.lflags[""] = ["-b", "-c"];
645 	auto sdl = toSDL(p).toSDLDocument();
646 	assert(sdl ==
647 `name "test"
648 authors "foo" "bar"
649 dflags "-a" platform="windows"
650 lflags "-b" "-c"
651 `);
652 }
653 
654 unittest {
655 	auto sdl = "name \"test\"\nsourcePaths";
656 	PackageRecipe rec;
657 	parseSDL(rec, sdl, null, "testfile");
658 	assert("" in rec.buildSettings.sourcePaths);
659 }
660 
661 unittest {
662 	auto sdl =
663 `name "test"
664 dependency "package" repository="git+https://some.url" version="12345678"
665 `;
666 	PackageRecipe rec;
667 	parseSDL(rec, sdl, null, "testfile");
668 	auto dependency = rec.buildSettings.dependencies["package"];
669 	assert(!dependency.repository.empty);
670 	assert(dependency.repository.ref_ == "12345678");
671 }
672 
673 unittest {
674 	PackageRecipe p;
675 	p.name = "test";
676 
677 	auto repository = Repository("git+https://some.url", "12345678");
678 	p.buildSettings.dependencies["package"] = Dependency(repository);
679 	auto sdl = toSDL(p).toSDLDocument();
680 	assert(sdl ==
681 `name "test"
682 dependency "package" repository="git+https://some.url" version="12345678"
683 `);
684 }