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