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