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