Newer
Older
dub_jkp / source / dub / recipe / json.d
  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 "toolchainRequirements":
  45. recipe.toolchainRequirements.parseJson(value);
  46. break;
  47. case "-ddoxFilterArgs": recipe.ddoxFilterArgs = deserializeJson!(string[])(value); break;
  48. case "-ddoxTool": recipe.ddoxTool = value.get!string; break;
  49. }
  50. }
  51.  
  52. enforce(recipe.name.length > 0, "The package \"name\" field is missing or empty.");
  53.  
  54. auto fullname = parent_name.length ? parent_name ~ ":" ~ recipe.name : recipe.name;
  55.  
  56. // parse build settings
  57. recipe.buildSettings.parseJson(json, fullname);
  58.  
  59. if (auto pv = "configurations" in json) {
  60. TargetType deftargettp = TargetType.library;
  61. if (recipe.buildSettings.targetType != TargetType.autodetect)
  62. deftargettp = recipe.buildSettings.targetType;
  63.  
  64. foreach (settings; *pv) {
  65. ConfigurationInfo ci;
  66. ci.parseJson(settings, recipe.name, deftargettp);
  67. recipe.configurations ~= ci;
  68. }
  69. }
  70.  
  71. // parse any sub packages after the main package has been fully parsed
  72. if (auto ps = "subPackages" in json)
  73. recipe.parseSubPackages(fullname, ps.opt!(Json[]));
  74. }
  75.  
  76. Json toJson(in ref PackageRecipe recipe)
  77. {
  78. auto ret = recipe.buildSettings.toJson();
  79. ret["name"] = recipe.name;
  80. if (!recipe.version_.empty) ret["version"] = recipe.version_;
  81. if (!recipe.description.empty) ret["description"] = recipe.description;
  82. if (!recipe.homepage.empty) ret["homepage"] = recipe.homepage;
  83. if (!recipe.authors.empty) ret["authors"] = serializeToJson(recipe.authors);
  84. if (!recipe.copyright.empty) ret["copyright"] = recipe.copyright;
  85. if (!recipe.license.empty) ret["license"] = recipe.license;
  86. if (!recipe.subPackages.empty) {
  87. Json[] jsonSubPackages = new Json[recipe.subPackages.length];
  88. foreach (i, subPackage; recipe.subPackages) {
  89. if (subPackage.path !is null) {
  90. jsonSubPackages[i] = Json(subPackage.path);
  91. } else {
  92. jsonSubPackages[i] = subPackage.recipe.toJson();
  93. }
  94. }
  95. ret["subPackages"] = jsonSubPackages;
  96. }
  97. if (recipe.configurations.length) {
  98. Json[] configs;
  99. foreach(config; recipe.configurations)
  100. configs ~= config.toJson();
  101. ret["configurations"] = configs;
  102. }
  103. if (recipe.buildTypes.length) {
  104. Json[string] types;
  105. foreach (name, settings; recipe.buildTypes)
  106. types[name] = settings.toJson();
  107. ret["buildTypes"] = types;
  108. }
  109. if (!recipe.toolchainRequirements.empty) {
  110. ret["toolchainRequirements"] = recipe.toolchainRequirements.toJson();
  111. }
  112. if (!recipe.ddoxFilterArgs.empty) ret["-ddoxFilterArgs"] = recipe.ddoxFilterArgs.serializeToJson();
  113. if (!recipe.ddoxTool.empty) ret["-ddoxTool"] = recipe.ddoxTool;
  114. return ret;
  115. }
  116.  
  117. private void parseSubPackages(ref PackageRecipe recipe, string parent_package_name, Json[] subPackagesJson)
  118. {
  119. enforce(!parent_package_name.canFind(":"), format("'subPackages' found in '%s'. This is only supported in the main package file for '%s'.",
  120. parent_package_name, getBasePackageName(parent_package_name)));
  121.  
  122. recipe.subPackages = new SubPackage[subPackagesJson.length];
  123. foreach (i, subPackageJson; subPackagesJson) {
  124. // Handle referenced Packages
  125. if(subPackageJson.type == Json.Type.string) {
  126. string subpath = subPackageJson.get!string;
  127. recipe.subPackages[i] = SubPackage(subpath, PackageRecipe.init);
  128. } else {
  129. PackageRecipe subinfo;
  130. subinfo.parseJson(subPackageJson, parent_package_name);
  131. recipe.subPackages[i] = SubPackage(null, subinfo);
  132. }
  133. }
  134. }
  135.  
  136. private void parseJson(ref ConfigurationInfo config, Json json, string package_name, TargetType default_target_type = TargetType.library)
  137. {
  138. config.buildSettings.targetType = default_target_type;
  139.  
  140. foreach (string name, value; json) {
  141. switch (name) {
  142. default: break;
  143. case "name":
  144. config.name = value.get!string;
  145. enforce(!config.name.empty, "Configurations must have a non-empty name.");
  146. break;
  147. case "platforms": config.platforms = deserializeJson!(string[])(value); break;
  148. }
  149. }
  150.  
  151. enforce(!config.name.empty, "Configuration is missing a name.");
  152.  
  153. BuildSettingsTemplate bs;
  154. config.buildSettings.parseJson(json, package_name);
  155. }
  156.  
  157. private Json toJson(in ref ConfigurationInfo config)
  158. {
  159. auto ret = config.buildSettings.toJson();
  160. ret["name"] = config.name;
  161. if (config.platforms.length) ret["platforms"] = serializeToJson(config.platforms);
  162. return ret;
  163. }
  164.  
  165. private void parseJson(ref BuildSettingsTemplate bs, Json json, string package_name)
  166. {
  167. foreach(string name, value; json)
  168. {
  169. auto idx = indexOf(name, "-");
  170. string basename, suffix;
  171. if( idx >= 0 ) { basename = name[0 .. idx]; suffix = name[idx .. $]; }
  172. else basename = name;
  173. switch(basename){
  174. default: break;
  175. case "dependencies":
  176. foreach (string pkg, verspec; value) {
  177. if (pkg.startsWith(":")) {
  178. enforce(!package_name.canFind(':'), format("Short-hand packages syntax not allowed within sub packages: %s -> %s", package_name, pkg));
  179. pkg = package_name ~ pkg;
  180. }
  181. enforce(pkg !in bs.dependencies, "The dependency '"~pkg~"' is specified more than once." );
  182. bs.dependencies[pkg] = deserializeJson!Dependency(verspec);
  183. }
  184. break;
  185. case "systemDependencies":
  186. bs.systemDependencies = value.get!string;
  187. break;
  188. case "targetType":
  189. enforce(suffix.empty, "targetType does not support platform customization.");
  190. bs.targetType = value.get!string.to!TargetType;
  191. break;
  192. case "targetPath":
  193. enforce(suffix.empty, "targetPath does not support platform customization.");
  194. bs.targetPath = value.get!string;
  195. break;
  196. case "targetName":
  197. enforce(suffix.empty, "targetName does not support platform customization.");
  198. bs.targetName = value.get!string;
  199. break;
  200. case "workingDirectory":
  201. enforce(suffix.empty, "workingDirectory does not support platform customization.");
  202. bs.workingDirectory = value.get!string;
  203. break;
  204. case "mainSourceFile":
  205. enforce(suffix.empty, "mainSourceFile does not support platform customization.");
  206. bs.mainSourceFile = value.get!string;
  207. break;
  208. case "subConfigurations":
  209. enforce(suffix.empty, "subConfigurations does not support platform customization.");
  210. bs.subConfigurations = deserializeJson!(string[string])(value);
  211. break;
  212. case "dflags": bs.dflags[suffix] = deserializeJson!(string[])(value); break;
  213. case "lflags": bs.lflags[suffix] = deserializeJson!(string[])(value); break;
  214. case "libs": bs.libs[suffix] = deserializeJson!(string[])(value); break;
  215. case "files":
  216. case "sourceFiles": bs.sourceFiles[suffix] = deserializeJson!(string[])(value); break;
  217. case "sourcePaths": bs.sourcePaths[suffix] = deserializeJson!(string[])(value); break;
  218. case "sourcePath": bs.sourcePaths[suffix] ~= [value.get!string]; break; // deprecated
  219. case "excludedSourceFiles": bs.excludedSourceFiles[suffix] = deserializeJson!(string[])(value); break;
  220. case "copyFiles": bs.copyFiles[suffix] = deserializeJson!(string[])(value); break;
  221. case "extraDependencyFiles": bs.extraDependencyFiles[suffix] = deserializeJson!(string[])(value); break;
  222. case "versions": bs.versions[suffix] = deserializeJson!(string[])(value); break;
  223. case "debugVersions": bs.debugVersions[suffix] = deserializeJson!(string[])(value); break;
  224. case "-versionFilters": bs.versionFilters[suffix] = deserializeJson!(string[])(value); break;
  225. case "-debugVersionFilters": bs.debugVersionFilters[suffix] = deserializeJson!(string[])(value); break;
  226. case "importPaths": bs.importPaths[suffix] = deserializeJson!(string[])(value); break;
  227. case "stringImportPaths": bs.stringImportPaths[suffix] = deserializeJson!(string[])(value); break;
  228. case "preGenerateCommands": bs.preGenerateCommands[suffix] = deserializeJson!(string[])(value); break;
  229. case "postGenerateCommands": bs.postGenerateCommands[suffix] = deserializeJson!(string[])(value); break;
  230. case "preBuildCommands": bs.preBuildCommands[suffix] = deserializeJson!(string[])(value); break;
  231. case "postBuildCommands": bs.postBuildCommands[suffix] = deserializeJson!(string[])(value); break;
  232. case "preRunCommands": bs.preRunCommands[suffix] = deserializeJson!(string[])(value); break;
  233. case "postRunCommands": bs.postRunCommands[suffix] = deserializeJson!(string[])(value); break;
  234. case "buildRequirements":
  235. BuildRequirements reqs;
  236. foreach (req; deserializeJson!(string[])(value))
  237. reqs |= to!BuildRequirement(req);
  238. bs.buildRequirements[suffix] = reqs;
  239. break;
  240. case "buildOptions":
  241. BuildOptions options;
  242. foreach (opt; deserializeJson!(string[])(value))
  243. options |= to!BuildOption(opt);
  244. bs.buildOptions[suffix] = options;
  245. break;
  246. }
  247. }
  248. }
  249.  
  250. private Json toJson(in ref BuildSettingsTemplate bs)
  251. {
  252. auto ret = Json.emptyObject;
  253. if( bs.dependencies !is null ){
  254. auto deps = Json.emptyObject;
  255. foreach( pack, d; bs.dependencies )
  256. deps[pack] = serializeToJson(d);
  257. ret["dependencies"] = deps;
  258. }
  259. if (bs.systemDependencies !is null) ret["systemDependencies"] = bs.systemDependencies;
  260. if (bs.targetType != TargetType.autodetect) ret["targetType"] = bs.targetType.to!string();
  261. if (!bs.targetPath.empty) ret["targetPath"] = bs.targetPath;
  262. if (!bs.targetName.empty) ret["targetName"] = bs.targetName;
  263. if (!bs.workingDirectory.empty) ret["workingDirectory"] = bs.workingDirectory;
  264. if (!bs.mainSourceFile.empty) ret["mainSourceFile"] = bs.mainSourceFile;
  265. if (bs.subConfigurations.length > 0) ret["subConfigurations"] = serializeToJson(bs.subConfigurations);
  266. foreach (suffix, arr; bs.dflags) ret["dflags"~suffix] = serializeToJson(arr);
  267. foreach (suffix, arr; bs.lflags) ret["lflags"~suffix] = serializeToJson(arr);
  268. foreach (suffix, arr; bs.libs) ret["libs"~suffix] = serializeToJson(arr);
  269. foreach (suffix, arr; bs.sourceFiles) ret["sourceFiles"~suffix] = serializeToJson(arr);
  270. foreach (suffix, arr; bs.sourcePaths) ret["sourcePaths"~suffix] = serializeToJson(arr);
  271. foreach (suffix, arr; bs.excludedSourceFiles) ret["excludedSourceFiles"~suffix] = serializeToJson(arr);
  272. foreach (suffix, arr; bs.copyFiles) ret["copyFiles"~suffix] = serializeToJson(arr);
  273. foreach (suffix, arr; bs.extraDependencyFiles) ret["extraDependencyFiles"~suffix] = serializeToJson(arr);
  274. foreach (suffix, arr; bs.versions) ret["versions"~suffix] = serializeToJson(arr);
  275. foreach (suffix, arr; bs.debugVersions) ret["debugVersions"~suffix] = serializeToJson(arr);
  276. foreach (suffix, arr; bs.versionFilters) ret["-versionFilters"~suffix] = serializeToJson(arr);
  277. foreach (suffix, arr; bs.debugVersionFilters) ret["-debugVersionFilters"~suffix] = serializeToJson(arr);
  278. foreach (suffix, arr; bs.importPaths) ret["importPaths"~suffix] = serializeToJson(arr);
  279. foreach (suffix, arr; bs.stringImportPaths) ret["stringImportPaths"~suffix] = serializeToJson(arr);
  280. foreach (suffix, arr; bs.preGenerateCommands) ret["preGenerateCommands"~suffix] = serializeToJson(arr);
  281. foreach (suffix, arr; bs.postGenerateCommands) ret["postGenerateCommands"~suffix] = serializeToJson(arr);
  282. foreach (suffix, arr; bs.preBuildCommands) ret["preBuildCommands"~suffix] = serializeToJson(arr);
  283. foreach (suffix, arr; bs.postBuildCommands) ret["postBuildCommands"~suffix] = serializeToJson(arr);
  284. foreach (suffix, arr; bs.preRunCommands) ret["preRunCommands"~suffix] = serializeToJson(arr);
  285. foreach (suffix, arr; bs.postRunCommands) ret["postRunCommands"~suffix] = serializeToJson(arr);
  286. foreach (suffix, arr; bs.buildRequirements) {
  287. string[] val;
  288. foreach (i; [EnumMembers!BuildRequirement])
  289. if (arr & i) val ~= to!string(i);
  290. ret["buildRequirements"~suffix] = serializeToJson(val);
  291. }
  292. foreach (suffix, arr; bs.buildOptions) {
  293. string[] val;
  294. foreach (i; [EnumMembers!BuildOption])
  295. if (arr & i) val ~= to!string(i);
  296. ret["buildOptions"~suffix] = serializeToJson(val);
  297. }
  298. return ret;
  299. }
  300.  
  301. private void parseJson(ref ToolchainRequirements tr, Json json)
  302. {
  303. foreach (string name, value; json)
  304. tr.addRequirement(name, value.get!string);
  305. }
  306.  
  307. private Json toJson(in ref ToolchainRequirements tr)
  308. {
  309. auto ret = Json.emptyObject;
  310. if (tr.dub != Dependency.any) ret["dub"] = serializeToJson(tr.dub);
  311. if (tr.frontend != Dependency.any) ret["frontend"] = serializeToJson(tr.frontend);
  312. if (tr.dmd != Dependency.any) ret["dmd"] = serializeToJson(tr.dmd);
  313. if (tr.ldc != Dependency.any) ret["ldc"] = serializeToJson(tr.ldc);
  314. if (tr.gdc != Dependency.any) ret["gdc"] = serializeToJson(tr.gdc);
  315. return ret;
  316. }