Newer
Older
dub_jkp / source / dub / recipe / sdl.d
/**
	SDL format support for PackageRecipe

	Copyright: © 2014-2015 rejectedsoftware e.K.
	License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file.
	Authors: Sönke Ludwig
*/
module dub.recipe.sdl;

import dub.compilers.compiler;
import dub.dependency;
import dub.internal.sdlang;
import dub.internal.vibecompat.core.log;
import dub.internal.vibecompat.inet.path;
import dub.recipe.packagerecipe;

import std.conv;
import std.string : startsWith;


void parseSDL(ref PackageRecipe recipe, string sdl, string parent_name, string filename)
{
	parseSDL(recipe, parseSource(sdl, filename), parent_name);
}

void parseSDL(ref PackageRecipe recipe, Tag sdl, string parent_name)
{
	Tag[] subpacks;
	Tag[] configs;

	// parse top-level fields
	foreach (n; sdl.tags) {
		enforceSDL(n.name.length > 0, "Anonymous tags are not allowed at the root level.", n);
		switch (n.fullName) {
			default: break;
			case "name": recipe.name = n.stringTagValue; break;
			case "description": recipe.description = n.stringTagValue; break;
			case "homepage": recipe.homepage = n.stringTagValue; break;
			case "authors": recipe.authors = n.stringArrayTagValue; break;
			case "copyright": recipe.copyright = n.stringTagValue; break;
			case "license": recipe.license = n.stringTagValue; break;
			case "subPackage": subpacks ~= n; break;
			case "configuration": configs ~= n; break;
			case "buildType":
				auto name = n.stringTagValue(true);
				BuildSettingsTemplate bt;
				parseBuildSettings(n, bt, parent_name);
				recipe.buildTypes[name] = bt;
				break;
			case "x:ddoxFilterArgs": recipe.ddoxFilterArgs = n.stringArrayTagValue; break;
		}
	}

	enforce(recipe.name.length > 0, "The package \"name\" field is missing or empty.");
	string full_name = parent_name.length ? parent_name ~ ":" ~ recipe.name : recipe.name;

	// parse general build settings
	parseBuildSettings(sdl, recipe.buildSettings, full_name);

	// parse configurations
	recipe.configurations.length = configs.length;
	foreach (i, n; configs)
		parseConfiguration(n, recipe.configurations[i], full_name);

	// finally parse all sub packages
	recipe.subPackages.length = subpacks.length;
	foreach (i, n; subpacks) {
		if (n.values.length) {
			recipe.subPackages[i].path = n.stringTagValue;
		} else {
			enforceSDL(n.attributes.length == 0, "No attributes allowed for inline sub package definitions.", n);
			parseSDL(recipe.subPackages[i].recipe, n, full_name);
		}
	}
}

private void parseBuildSettings(Tag settings, ref BuildSettingsTemplate bs, string package_name)
{
	foreach (setting; settings.tags) {
		parseBuildSetting(setting, bs, package_name);
	}
}

private void parseBuildSetting(Tag setting, ref BuildSettingsTemplate bs, string package_name)
{
	switch (setting.fullName) {
		default: break;
		case "dependency": parseDependency(setting, bs, package_name); break;
		case "systemDependencies": bs.systemDependencies = setting.stringTagValue; break;
		case "targetType": bs.targetType = setting.stringTagValue.to!TargetType; break;
		case "targetName": bs.targetName = setting.stringTagValue; break;
		case "targetPath": bs.targetPath = setting.stringTagValue; break;
		case "workingDirectory": bs.workingDirectory = setting.stringTagValue; break;
		case "subConfiguration":
			auto args = setting.stringArrayTagValue;
			enforceSDL(args.length == 2, "Expecting package and configuration names as arguments.", setting);
			bs.subConfigurations[args[0]] = args[1];
			break;
		case "dflags": setting.parsePlatformStringArray(bs.dflags); break;
		case "lflags": setting.parsePlatformStringArray(bs.lflags); break;
		case "libs": setting.parsePlatformStringArray(bs.libs); break;
		case "sourceFiles": setting.parsePlatformStringArray(bs.sourceFiles); break;
		case "sourcePaths": setting.parsePlatformStringArray(bs.sourcePaths); break;
		case "excludedSourceFiles": setting.parsePlatformStringArray(bs.excludedSourceFiles); break;
		case "copyFiles": setting.parsePlatformStringArray(bs.copyFiles); break;
		case "versions": setting.parsePlatformStringArray(bs.versions); break;
		case "importPaths": setting.parsePlatformStringArray(bs.importPaths); break;
		case "stringImportPaths": setting.parsePlatformStringArray(bs.stringImportPaths); break;
		case "preGenerateCommands": setting.parsePlatformStringArray(bs.preGenerateCommands); break;
		case "postGenerateCommands": setting.parsePlatformStringArray(bs.postGenerateCommands); break;
		case "preBuildCommands": setting.parsePlatformStringArray(bs.preBuildCommands); break;
		case "postBuildCommands": setting.parsePlatformStringArray(bs.postBuildCommands); break;
		case "buildRequirements": setting.parsePlatformEnumArray!BuildRequirements(bs.buildRequirements); break;
		case "buildOptions": setting.parsePlatformEnumArray!BuildOptions(bs.buildOptions); break;
	}
}

private void parseDependency(Tag t, ref BuildSettingsTemplate bs, string package_name)
{
	import std.algorithm : canFind;
	import std.string : format;

	auto pkg = t.values[0].get!string;
	if (pkg.startsWith(":")) {
		enforce(!package_name.canFind(':'), format("Short-hand packages syntax not allowed within sub packages: %s -> %s", package_name, pkg));
		pkg = package_name ~ pkg;
	}
	enforce(pkg !in bs.dependencies, "The dependency '"~pkg~"' is specified more than once." );

	Dependency dep = Dependency.ANY;
	auto attrs = t.attributes;

	auto pv = "version" in attrs;

	if ("path" in attrs) {
		if ("version" in attrs)
			logDiagnostic("Ignoring version specification (%s) for path based dependency %s", attrs["version"][0].value.get!string, attrs["path"][0].value.get!string);
		dep.versionSpec = "*";
		dep.path = Path(attrs["path"][0].value.get!string);
	} else {
		enforceSDL("version" in attrs, "Missing version specification.", t);
		dep.versionSpec = attrs["version"][0].value.get!string;
	}

	if ("optional" in attrs)
		dep.optional = attrs["optional"][0].value.get!bool;

	bs.dependencies[pkg] = dep;
}

private void parseConfiguration(Tag t, ref ConfigurationInfo ret, string package_name)
{
	ret.name = t.stringTagValue(true);
	foreach (f; t.tags) {
		switch (f.fullName) {
			default: parseBuildSetting(f, ret.buildSettings, package_name); break;
			case "platforms": ret.platforms = f.stringArrayTagValue; break;
		}
	}
}

private string stringTagValue(Tag t, bool allow_child_tags = false)
{
	// TODO: check for additional values or attributes
	return t.values[0].get!string;
}

private string[] stringArrayTagValue(Tag t, bool allow_child_tags = false)
{
	string[] ret;
	// TODO: check for additional attributes
	foreach (v; t.values)
		ret ~= v.get!string;
	return ret;
}

private void parsePlatformStringArray(Tag t, ref string[][string] dst)
{
	string platform;
	if ("platform" in t.attributes)
		platform = t.attributes["platform"][0].value.get!string;
	foreach (v; t.values)
		dst[platform] ~= v.get!string;
}

private void parsePlatformEnumArray(E, Es)(Tag t, ref Es[string] dst)
{
	string platform;
	if ("platform" in t.attributes)
		platform = t.attributes["platform"][0].value.get!string;
	foreach (v; t.values)
		dst[platform] |= v.get!string.to!E;
}

private void enforceSDL(bool condition, lazy string message, Tag tag, string file = __FILE__, int line = __LINE__)
{
	import std.string : format;
	if (!condition) {
		throw new Exception(format("%s(%s): Error: %s", tag.location.file, tag.location.line, message), file, line);
	}
}