diff --git a/source/dub/package_.d b/source/dub/package_.d index 3f70a75..d353426 100644 --- a/source/dub/package_.d +++ b/source/dub/package_.d @@ -48,7 +48,7 @@ // Supported package descriptions in decreasing order of preference. static immutable FilenameAndFormat[] packageInfoFiles = [ {"dub.json", PackageFormat.json}, - /*{"dub.sdl",PackageFormat.sdl},*/ + {"dub.sdl",PackageFormat.sdl}, {"package.json", PackageFormat.json} ]; @@ -507,7 +507,7 @@ case PackageFormat.json: return new JsonPackage(parseJsonString(text, file.path.toNativeString())); case PackageFormat.sdl: - if(silent_fail) return null; throw new Exception("SDL not implemented"); + return new SdlPackage(text, file.path.toNativeString()); } } @@ -543,11 +543,32 @@ recipe.parseJson(json, parent_name); } } + private static class SdlPackage : RawPackage { - override void parseInto(ref PackageRecipe package_, string parent_name) + import dub.internal.sdlang; + Tag sdl; + + this(string sdl_text, string filename) { - throw new Exception("SDL packages not implemented yet"); + this.sdl = parseSource(sdl_text, filename); + foreach (t; this.sdl.tags) { + switch (t.name) { + default: break; + case "name": + this.package_name = t.values[0].get!string.toLower(); + t.values[0] = this.package_name; + break; + case "version": + this.version_ = t.values[0].get!string; + break; + } + } + } + + override void parseInto(ref PackageRecipe recipe, string parent_name) + { + recipe.parseSDL(sdl, parent_name); } } } diff --git a/source/dub/recipe/sdl.d b/source/dub/recipe/sdl.d index 3987567..c40250f 100644 --- a/source/dub/recipe/sdl.d +++ b/source/dub/recipe/sdl.d @@ -1,17 +1,201 @@ /** SDL format support for PackageRecipe - Copyright: © 2014 rejectedsoftware e.K. + 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; -alias SDLNode = void*; // TODO +import std.conv; +import std.string : startsWith; -void parseSDL(ref PackageRecipe recipe, SDLNode json, string parent_name) + +void parseSDL(ref PackageRecipe recipe, string sdl, string parent_name, string filename) { - assert(false); + 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); + } }