1 /** 2 JSON format support for PackageRecipe 3 4 Copyright: © 2012-2014 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, Matthias Dondorff 7 */ 8 module dub.recipe.json; 9 10 import dub.compilers.compiler; 11 import dub.dependency; 12 import dub.recipe.packagerecipe; 13 14 import dub.internal.vibecompat.data.json; 15 16 import std.algorithm : canFind, startsWith; 17 import std.conv : to; 18 import std.exception : enforce; 19 import std.range; 20 import std.string : format, indexOf; 21 import std.traits : EnumMembers; 22 23 24 void parseJson(ref PackageRecipe recipe, Json json, string parent_name) 25 { 26 foreach (string field, value; json) { 27 switch (field) { 28 default: break; 29 case "name": recipe.name = value.get!string; break; 30 case "version": recipe.version_ = value.get!string; break; 31 case "description": recipe.description = value.get!string; break; 32 case "homepage": recipe.homepage = value.get!string; break; 33 case "authors": recipe.authors = deserializeJson!(string[])(value); break; 34 case "copyright": recipe.copyright = value.get!string; break; 35 case "license": recipe.license = value.get!string; break; 36 case "configurations": break; // handled below, after the global settings have been parsed 37 case "buildTypes": 38 foreach (string name, settings; value) { 39 BuildSettingsTemplate bs; 40 bs.parseJson(settings, null); 41 recipe.buildTypes[name] = bs; 42 } 43 break; 44 case "-ddoxFilterArgs": recipe.ddoxFilterArgs = deserializeJson!(string[])(value); break; 45 case "-ddoxTool": recipe.ddoxTool = value.get!string; break; 46 } 47 } 48 49 enforce(recipe.name.length > 0, "The package \"name\" field is missing or empty."); 50 51 auto fullname = parent_name.length ? parent_name ~ ":" ~ recipe.name : recipe.name; 52 53 // parse build settings 54 recipe.buildSettings.parseJson(json, fullname); 55 56 if (auto pv = "configurations" in json) { 57 TargetType deftargettp = TargetType.library; 58 if (recipe.buildSettings.targetType != TargetType.autodetect) 59 deftargettp = recipe.buildSettings.targetType; 60 61 foreach (settings; *pv) { 62 ConfigurationInfo ci; 63 ci.parseJson(settings, recipe.name, deftargettp); 64 recipe.configurations ~= ci; 65 } 66 } 67 68 // parse any sub packages after the main package has been fully parsed 69 if (auto ps = "subPackages" in json) 70 recipe.parseSubPackages(fullname, ps.opt!(Json[])); 71 } 72 73 Json toJson(in ref PackageRecipe recipe) 74 { 75 auto ret = recipe.buildSettings.toJson(); 76 ret["name"] = recipe.name; 77 if (!recipe.version_.empty) ret["version"] = recipe.version_; 78 if (!recipe.description.empty) ret["description"] = recipe.description; 79 if (!recipe.homepage.empty) ret["homepage"] = recipe.homepage; 80 if (!recipe.authors.empty) ret["authors"] = serializeToJson(recipe.authors); 81 if (!recipe.copyright.empty) ret["copyright"] = recipe.copyright; 82 if (!recipe.license.empty) ret["license"] = recipe.license; 83 if (!recipe.subPackages.empty) { 84 Json[] jsonSubPackages = new Json[recipe.subPackages.length]; 85 foreach (i, subPackage; recipe.subPackages) { 86 if (subPackage.path !is null) { 87 jsonSubPackages[i] = Json(subPackage.path); 88 } else { 89 jsonSubPackages[i] = subPackage.recipe.toJson(); 90 } 91 } 92 ret["subPackages"] = jsonSubPackages; 93 } 94 if (recipe.configurations.length) { 95 Json[] configs; 96 foreach(config; recipe.configurations) 97 configs ~= config.toJson(); 98 ret["configurations"] = configs; 99 } 100 if (recipe.buildTypes.length) { 101 Json[string] types; 102 foreach (name, settings; recipe.buildTypes) 103 types[name] = settings.toJson(); 104 ret["buildTypes"] = types; 105 } 106 if (!recipe.ddoxFilterArgs.empty) ret["-ddoxFilterArgs"] = recipe.ddoxFilterArgs.serializeToJson(); 107 if (!recipe.ddoxTool.empty) ret["-ddoxTool"] = recipe.ddoxTool; 108 return ret; 109 } 110 111 private void parseSubPackages(ref PackageRecipe recipe, string parent_package_name, Json[] subPackagesJson) 112 { 113 enforce(!parent_package_name.canFind(":"), format("'subPackages' found in '%s'. This is only supported in the main package file for '%s'.", 114 parent_package_name, getBasePackageName(parent_package_name))); 115 116 recipe.subPackages = new SubPackage[subPackagesJson.length]; 117 foreach (i, subPackageJson; subPackagesJson) { 118 // Handle referenced Packages 119 if(subPackageJson.type == Json.Type..string) { 120 string subpath = subPackageJson.get!string; 121 recipe.subPackages[i] = SubPackage(subpath, PackageRecipe.init); 122 } else { 123 PackageRecipe subinfo; 124 subinfo.parseJson(subPackageJson, parent_package_name); 125 recipe.subPackages[i] = SubPackage(null, subinfo); 126 } 127 } 128 } 129 130 private void parseJson(ref ConfigurationInfo config, Json json, string package_name, TargetType default_target_type = TargetType.library) 131 { 132 config.buildSettings.targetType = default_target_type; 133 134 foreach (string name, value; json) { 135 switch (name) { 136 default: break; 137 case "name": 138 config.name = value.get!string; 139 enforce(!config.name.empty, "Configurations must have a non-empty name."); 140 break; 141 case "platforms": config.platforms = deserializeJson!(string[])(value); break; 142 } 143 } 144 145 enforce(!config.name.empty, "Configuration is missing a name."); 146 147 BuildSettingsTemplate bs; 148 config.buildSettings.parseJson(json, package_name); 149 } 150 151 private Json toJson(in ref ConfigurationInfo config) 152 { 153 auto ret = config.buildSettings.toJson(); 154 ret["name"] = config.name; 155 if (config.platforms.length) ret["platforms"] = serializeToJson(config.platforms); 156 return ret; 157 } 158 159 private void parseJson(ref BuildSettingsTemplate bs, Json json, string package_name) 160 { 161 foreach(string name, value; json) 162 { 163 auto idx = indexOf(name, "-"); 164 string basename, suffix; 165 if( idx >= 0 ) { basename = name[0 .. idx]; suffix = name[idx .. $]; } 166 else basename = name; 167 switch(basename){ 168 default: break; 169 case "dependencies": 170 foreach (string pkg, verspec; value) { 171 if (pkg.startsWith(":")) { 172 enforce(!package_name.canFind(':'), format("Short-hand packages syntax not allowed within sub packages: %s -> %s", package_name, pkg)); 173 pkg = package_name ~ pkg; 174 } 175 enforce(pkg !in bs.dependencies, "The dependency '"~pkg~"' is specified more than once." ); 176 bs.dependencies[pkg] = deserializeJson!Dependency(verspec); 177 } 178 break; 179 case "systemDependencies": 180 bs.systemDependencies = value.get!string; 181 break; 182 case "targetType": 183 enforce(suffix.empty, "targetType does not support platform customization."); 184 bs.targetType = value.get!string.to!TargetType; 185 break; 186 case "targetPath": 187 enforce(suffix.empty, "targetPath does not support platform customization."); 188 bs.targetPath = value.get!string; 189 break; 190 case "targetName": 191 enforce(suffix.empty, "targetName does not support platform customization."); 192 bs.targetName = value.get!string; 193 break; 194 case "workingDirectory": 195 enforce(suffix.empty, "workingDirectory does not support platform customization."); 196 bs.workingDirectory = value.get!string; 197 break; 198 case "mainSourceFile": 199 enforce(suffix.empty, "mainSourceFile does not support platform customization."); 200 bs.mainSourceFile = value.get!string; 201 break; 202 case "subConfigurations": 203 enforce(suffix.empty, "subConfigurations does not support platform customization."); 204 bs.subConfigurations = deserializeJson!(string[string])(value); 205 break; 206 case "dflags": bs.dflags[suffix] = deserializeJson!(string[])(value); break; 207 case "lflags": bs.lflags[suffix] = deserializeJson!(string[])(value); break; 208 case "libs": bs.libs[suffix] = deserializeJson!(string[])(value); break; 209 case "files": 210 case "sourceFiles": bs.sourceFiles[suffix] = deserializeJson!(string[])(value); break; 211 case "sourcePaths": bs.sourcePaths[suffix] = deserializeJson!(string[])(value); break; 212 case "sourcePath": bs.sourcePaths[suffix] ~= [value.get!string]; break; // deprecated 213 case "excludedSourceFiles": bs.excludedSourceFiles[suffix] = deserializeJson!(string[])(value); break; 214 case "copyFiles": bs.copyFiles[suffix] = deserializeJson!(string[])(value); break; 215 case "versions": bs.versions[suffix] = deserializeJson!(string[])(value); break; 216 case "debugVersions": bs.debugVersions[suffix] = deserializeJson!(string[])(value); break; 217 case "importPaths": bs.importPaths[suffix] = deserializeJson!(string[])(value); break; 218 case "stringImportPaths": bs.stringImportPaths[suffix] = deserializeJson!(string[])(value); break; 219 case "preGenerateCommands": bs.preGenerateCommands[suffix] = deserializeJson!(string[])(value); break; 220 case "postGenerateCommands": bs.postGenerateCommands[suffix] = deserializeJson!(string[])(value); break; 221 case "preBuildCommands": bs.preBuildCommands[suffix] = deserializeJson!(string[])(value); break; 222 case "postBuildCommands": bs.postBuildCommands[suffix] = deserializeJson!(string[])(value); break; 223 case "buildRequirements": 224 BuildRequirements reqs; 225 foreach (req; deserializeJson!(string[])(value)) 226 reqs |= to!BuildRequirement(req); 227 bs.buildRequirements[suffix] = reqs; 228 break; 229 case "buildOptions": 230 BuildOptions options; 231 foreach (opt; deserializeJson!(string[])(value)) 232 options |= to!BuildOption(opt); 233 bs.buildOptions[suffix] = options; 234 break; 235 } 236 } 237 } 238 239 private Json toJson(in ref BuildSettingsTemplate bs) 240 { 241 auto ret = Json.emptyObject; 242 if( bs.dependencies !is null ){ 243 auto deps = Json.emptyObject; 244 foreach( pack, d; bs.dependencies ) 245 deps[pack] = serializeToJson(d); 246 ret["dependencies"] = deps; 247 } 248 if (bs.systemDependencies !is null) ret["systemDependencies"] = bs.systemDependencies; 249 if (bs.targetType != TargetType.autodetect) ret["targetType"] = bs.targetType.to!string(); 250 if (!bs.targetPath.empty) ret["targetPath"] = bs.targetPath; 251 if (!bs.targetName.empty) ret["targetName"] = bs.targetName; 252 if (!bs.workingDirectory.empty) ret["workingDirectory"] = bs.workingDirectory; 253 if (!bs.mainSourceFile.empty) ret["mainSourceFile"] = bs.mainSourceFile; 254 if (bs.subConfigurations.length > 0) ret["subConfigurations"] = serializeToJson(bs.subConfigurations); 255 foreach (suffix, arr; bs.dflags) ret["dflags"~suffix] = serializeToJson(arr); 256 foreach (suffix, arr; bs.lflags) ret["lflags"~suffix] = serializeToJson(arr); 257 foreach (suffix, arr; bs.libs) ret["libs"~suffix] = serializeToJson(arr); 258 foreach (suffix, arr; bs.sourceFiles) ret["sourceFiles"~suffix] = serializeToJson(arr); 259 foreach (suffix, arr; bs.sourcePaths) ret["sourcePaths"~suffix] = serializeToJson(arr); 260 foreach (suffix, arr; bs.excludedSourceFiles) ret["excludedSourceFiles"~suffix] = serializeToJson(arr); 261 foreach (suffix, arr; bs.copyFiles) ret["copyFiles"~suffix] = serializeToJson(arr); 262 foreach (suffix, arr; bs.versions) ret["versions"~suffix] = serializeToJson(arr); 263 foreach (suffix, arr; bs.debugVersions) ret["debugVersions"~suffix] = serializeToJson(arr); 264 foreach (suffix, arr; bs.importPaths) ret["importPaths"~suffix] = serializeToJson(arr); 265 foreach (suffix, arr; bs.stringImportPaths) ret["stringImportPaths"~suffix] = serializeToJson(arr); 266 foreach (suffix, arr; bs.preGenerateCommands) ret["preGenerateCommands"~suffix] = serializeToJson(arr); 267 foreach (suffix, arr; bs.postGenerateCommands) ret["postGenerateCommands"~suffix] = serializeToJson(arr); 268 foreach (suffix, arr; bs.preBuildCommands) ret["preBuildCommands"~suffix] = serializeToJson(arr); 269 foreach (suffix, arr; bs.postBuildCommands) ret["postBuildCommands"~suffix] = serializeToJson(arr); 270 foreach (suffix, arr; bs.buildRequirements) { 271 string[] val; 272 foreach (i; [EnumMembers!BuildRequirement]) 273 if (arr & i) val ~= to!string(i); 274 ret["buildRequirements"~suffix] = serializeToJson(val); 275 } 276 foreach (suffix, arr; bs.buildOptions) { 277 string[] val; 278 foreach (i; [EnumMembers!BuildOption]) 279 if (arr & i) val ~= to!string(i); 280 ret["buildOptions"~suffix] = serializeToJson(val); 281 } 282 return ret; 283 }