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