Newer
Older
dub_jkp / source / dub / recipe / json.d
@Mathias Lang Mathias Lang on 18 Jan 2024 17 KB Adapt dub.recipe.json to PackageName API
  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. deprecated("Use the overload that takes a `PackageName` as 3rd argument")
  24. void parseJson(ref PackageRecipe recipe, Json json, string parent)
  25. {
  26. const PackageName pname = parent ? PackageName(parent) : PackageName.init;
  27. parseJson(recipe, json, pname);
  28. }
  29.  
  30. void parseJson(ref PackageRecipe recipe, Json json, in PackageName parent = PackageName.init)
  31. {
  32. foreach (string field, value; json) {
  33. switch (field) {
  34. default: break;
  35. case "name": recipe.name = value.get!string; break;
  36. case "version": recipe.version_ = value.get!string; break;
  37. case "description": recipe.description = value.get!string; break;
  38. case "homepage": recipe.homepage = value.get!string; break;
  39. case "authors": recipe.authors = deserializeJson!(string[])(value); break;
  40. case "copyright": recipe.copyright = value.get!string; break;
  41. case "license": recipe.license = value.get!string; break;
  42. case "configurations": break; // handled below, after the global settings have been parsed
  43. case "buildTypes":
  44. foreach (string name, settings; value) {
  45. BuildSettingsTemplate bs;
  46. bs.parseJson(settings, PackageName.init);
  47. recipe.buildTypes[name] = bs;
  48. }
  49. break;
  50. case "toolchainRequirements":
  51. recipe.toolchainRequirements.parseJson(value);
  52. break;
  53. case "-ddoxFilterArgs": recipe.ddoxFilterArgs = deserializeJson!(string[])(value); break;
  54. case "-ddoxTool": recipe.ddoxTool = value.get!string; break;
  55. }
  56. }
  57.  
  58. enforce(recipe.name.length > 0, "The package \"name\" field is missing or empty.");
  59.  
  60. const fullname = parent.toString().length
  61. ? PackageName(parent.toString() ~ ":" ~ recipe.name)
  62. : PackageName(recipe.name);
  63.  
  64. // parse build settings
  65. recipe.buildSettings.parseJson(json, fullname);
  66.  
  67. if (auto pv = "configurations" in json) {
  68. foreach (settings; *pv) {
  69. ConfigurationInfo ci;
  70. ci.parseJson(settings, fullname);
  71. recipe.configurations ~= ci;
  72. }
  73. }
  74.  
  75. // parse any sub packages after the main package has been fully parsed
  76. if (auto ps = "subPackages" in json)
  77. recipe.parseSubPackages(fullname, ps.opt!(Json[]));
  78. }
  79.  
  80. Json toJson(const scope ref PackageRecipe recipe)
  81. {
  82. auto ret = recipe.buildSettings.toJson();
  83. ret["name"] = recipe.name;
  84. if (!recipe.version_.empty) ret["version"] = recipe.version_;
  85. if (!recipe.description.empty) ret["description"] = recipe.description;
  86. if (!recipe.homepage.empty) ret["homepage"] = recipe.homepage;
  87. if (!recipe.authors.empty) ret["authors"] = serializeToJson(recipe.authors);
  88. if (!recipe.copyright.empty) ret["copyright"] = recipe.copyright;
  89. if (!recipe.license.empty) ret["license"] = recipe.license;
  90. if (!recipe.subPackages.empty) {
  91. Json[] jsonSubPackages = new Json[recipe.subPackages.length];
  92. foreach (i, subPackage; recipe.subPackages) {
  93. if (subPackage.path !is null) {
  94. jsonSubPackages[i] = Json(subPackage.path);
  95. } else {
  96. jsonSubPackages[i] = subPackage.recipe.toJson();
  97. }
  98. }
  99. ret["subPackages"] = jsonSubPackages;
  100. }
  101. if (recipe.configurations.length) {
  102. Json[] configs;
  103. foreach(config; recipe.configurations)
  104. configs ~= config.toJson();
  105. ret["configurations"] = configs;
  106. }
  107. if (recipe.buildTypes.length) {
  108. Json[string] types;
  109. foreach (name, settings; recipe.buildTypes)
  110. types[name] = settings.toJson();
  111. ret["buildTypes"] = types;
  112. }
  113. if (!recipe.toolchainRequirements.empty) {
  114. ret["toolchainRequirements"] = recipe.toolchainRequirements.toJson();
  115. }
  116. if (!recipe.ddoxFilterArgs.empty) ret["-ddoxFilterArgs"] = recipe.ddoxFilterArgs.serializeToJson();
  117. if (!recipe.ddoxTool.empty) ret["-ddoxTool"] = recipe.ddoxTool;
  118. return ret;
  119. }
  120.  
  121. private void parseSubPackages(ref PackageRecipe recipe, in PackageName parent, Json[] subPackagesJson)
  122. {
  123. enforce(!parent.sub, format("'subPackages' found in '%s'. This is only supported in the main package file for '%s'.",
  124. parent, parent.main));
  125.  
  126. recipe.subPackages = new SubPackage[subPackagesJson.length];
  127. foreach (i, subPackageJson; subPackagesJson) {
  128. // Handle referenced Packages
  129. if(subPackageJson.type == Json.Type.string) {
  130. string subpath = subPackageJson.get!string;
  131. recipe.subPackages[i] = SubPackage(subpath, PackageRecipe.init);
  132. } else {
  133. PackageRecipe subinfo;
  134. subinfo.parseJson(subPackageJson, parent);
  135. recipe.subPackages[i] = SubPackage(null, subinfo);
  136. }
  137. }
  138. }
  139.  
  140. private void parseJson(ref ConfigurationInfo config, Json json, in PackageName pname)
  141. {
  142. foreach (string name, value; json) {
  143. switch (name) {
  144. default: break;
  145. case "name":
  146. config.name = value.get!string;
  147. enforce(!config.name.empty, "Configurations must have a non-empty name.");
  148. break;
  149. case "platforms": config.platforms = deserializeJson!(string[])(value); break;
  150. }
  151. }
  152.  
  153. enforce(!config.name.empty, "Configuration is missing a name.");
  154. config.buildSettings.parseJson(json, pname);
  155. }
  156.  
  157. private Json toJson(const scope 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, in PackageName pname)
  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 + 1 .. $]; }
  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(!pname.sub.length,
  179. "Short-hand packages syntax not allowed within " ~
  180. "sub packages: %s -> %s".format(pname, pkg));
  181. pkg = pname.toString() ~ pkg;
  182. }
  183. enforce(pkg !in bs.dependencies, "The dependency '"~pkg~"' is specified more than once." );
  184. bs.dependencies[pkg] = Dependency.fromJson(verspec);
  185. if (verspec.type == Json.Type.object)
  186. bs.dependencies[pkg].settings.parseJson(verspec, pname);
  187. }
  188. break;
  189. case "systemDependencies":
  190. bs.systemDependencies = value.get!string;
  191. break;
  192. case "targetType":
  193. enforce(suffix.empty, "targetType does not support platform customization.");
  194. bs.targetType = value.get!string.to!TargetType;
  195. break;
  196. case "targetPath":
  197. enforce(suffix.empty, "targetPath does not support platform customization.");
  198. bs.targetPath = value.get!string;
  199. break;
  200. case "targetName":
  201. enforce(suffix.empty, "targetName does not support platform customization.");
  202. bs.targetName = value.get!string;
  203. break;
  204. case "workingDirectory":
  205. enforce(suffix.empty, "workingDirectory does not support platform customization.");
  206. bs.workingDirectory = value.get!string;
  207. break;
  208. case "mainSourceFile":
  209. enforce(suffix.empty, "mainSourceFile does not support platform customization.");
  210. bs.mainSourceFile = value.get!string;
  211. break;
  212. case "subConfigurations":
  213. enforce(suffix.empty, "subConfigurations does not support platform customization.");
  214. bs.subConfigurations = deserializeJson!(string[string])(value);
  215. break;
  216. case "dflags": bs.dflags[suffix] = deserializeJson!(string[])(value); break;
  217. case "lflags": bs.lflags[suffix] = deserializeJson!(string[])(value); break;
  218. case "libs": bs.libs[suffix] = deserializeJson!(string[])(value); break;
  219. case "files":
  220. case "sourceFiles": bs.sourceFiles[suffix] = deserializeJson!(string[])(value); break;
  221. case "sourcePaths": bs.sourcePaths[suffix] = deserializeJson!(string[])(value); break;
  222. case "cSourcePaths": bs.cSourcePaths[suffix] = deserializeJson!(string[])(value); break;
  223. case "sourcePath": bs.sourcePaths[suffix] ~= [value.get!string]; break; // deprecated
  224. case "excludedSourceFiles": bs.excludedSourceFiles[suffix] = deserializeJson!(string[])(value); break;
  225. case "injectSourceFiles": bs.injectSourceFiles[suffix] = deserializeJson!(string[])(value); break;
  226. case "copyFiles": bs.copyFiles[suffix] = deserializeJson!(string[])(value); break;
  227. case "extraDependencyFiles": bs.extraDependencyFiles[suffix] = deserializeJson!(string[])(value); break;
  228. case "versions": bs.versions[suffix] = deserializeJson!(string[])(value); break;
  229. case "debugVersions": bs.debugVersions[suffix] = deserializeJson!(string[])(value); break;
  230. case "-versionFilters": bs.versionFilters[suffix] = deserializeJson!(string[])(value); break;
  231. case "-debugVersionFilters": bs.debugVersionFilters[suffix] = deserializeJson!(string[])(value); break;
  232. case "importPaths": bs.importPaths[suffix] = deserializeJson!(string[])(value); break;
  233. case "cImportPaths": bs.cImportPaths[suffix] = deserializeJson!(string[])(value); break;
  234. case "stringImportPaths": bs.stringImportPaths[suffix] = deserializeJson!(string[])(value); break;
  235. case "preGenerateCommands": bs.preGenerateCommands[suffix] = deserializeJson!(string[])(value); break;
  236. case "postGenerateCommands": bs.postGenerateCommands[suffix] = deserializeJson!(string[])(value); break;
  237. case "preBuildCommands": bs.preBuildCommands[suffix] = deserializeJson!(string[])(value); break;
  238. case "postBuildCommands": bs.postBuildCommands[suffix] = deserializeJson!(string[])(value); break;
  239. case "preRunCommands": bs.preRunCommands[suffix] = deserializeJson!(string[])(value); break;
  240. case "postRunCommands": bs.postRunCommands[suffix] = deserializeJson!(string[])(value); break;
  241. case "environments": bs.environments[suffix] = deserializeJson!(string[string])(value); break;
  242. case "buildEnvironments": bs.buildEnvironments[suffix] = deserializeJson!(string[string])(value); break;
  243. case "runEnvironments": bs.runEnvironments[suffix] = deserializeJson!(string[string])(value); break;
  244. case "preGenerateEnvironments": bs.preGenerateEnvironments[suffix] = deserializeJson!(string[string])(value); break;
  245. case "postGenerateEnvironments": bs.postGenerateEnvironments[suffix] = deserializeJson!(string[string])(value); break;
  246. case "preBuildEnvironments": bs.preBuildEnvironments[suffix] = deserializeJson!(string[string])(value); break;
  247. case "postBuildEnvironments": bs.postBuildEnvironments[suffix] = deserializeJson!(string[string])(value); break;
  248. case "preRunEnvironments": bs.preRunEnvironments[suffix] = deserializeJson!(string[string])(value); break;
  249. case "postRunEnvironments": bs.postRunEnvironments[suffix] = deserializeJson!(string[string])(value); break;
  250. case "buildRequirements":
  251. Flags!BuildRequirement reqs;
  252. foreach (req; deserializeJson!(string[])(value))
  253. reqs |= to!BuildRequirement(req);
  254. bs.buildRequirements[suffix] = reqs;
  255. break;
  256. case "buildOptions":
  257. Flags!BuildOption options;
  258. foreach (opt; deserializeJson!(string[])(value))
  259. options |= to!BuildOption(opt);
  260. bs.buildOptions[suffix] = options;
  261. break;
  262. }
  263. }
  264. }
  265.  
  266. private Json toJson(const scope ref BuildSettingsTemplate bs)
  267. {
  268. static string withSuffix (string pre, string post)
  269. {
  270. if (!post.length)
  271. return pre;
  272. return pre ~ "-" ~ post;
  273. }
  274.  
  275. auto ret = Json.emptyObject;
  276. if( bs.dependencies !is null ){
  277. auto deps = Json.emptyObject;
  278. foreach( pack, d; bs.dependencies )
  279. deps[pack] = d.toJson();
  280. ret["dependencies"] = deps;
  281. }
  282. if (bs.systemDependencies !is null) ret["systemDependencies"] = bs.systemDependencies;
  283. if (bs.targetType != TargetType.autodetect) ret["targetType"] = bs.targetType.to!string();
  284. if (!bs.targetPath.empty) ret["targetPath"] = bs.targetPath;
  285. if (!bs.targetName.empty) ret["targetName"] = bs.targetName;
  286. if (!bs.workingDirectory.empty) ret["workingDirectory"] = bs.workingDirectory;
  287. if (!bs.mainSourceFile.empty) ret["mainSourceFile"] = bs.mainSourceFile;
  288. if (bs.subConfigurations.length > 0) ret["subConfigurations"] = serializeToJson(bs.subConfigurations);
  289. foreach (suffix, arr; bs.dflags) ret[withSuffix("dflags", suffix)] = serializeToJson(arr);
  290. foreach (suffix, arr; bs.lflags) ret[withSuffix("lflags", suffix)] = serializeToJson(arr);
  291. foreach (suffix, arr; bs.libs) ret[withSuffix("libs", suffix)] = serializeToJson(arr);
  292. foreach (suffix, arr; bs.sourceFiles) ret[withSuffix("sourceFiles", suffix)] = serializeToJson(arr);
  293. foreach (suffix, arr; bs.sourcePaths) ret[withSuffix("sourcePaths", suffix)] = serializeToJson(arr);
  294. foreach (suffix, arr; bs.cSourcePaths) ret[withSuffix("cSourcePaths", suffix)] = serializeToJson(arr);
  295. foreach (suffix, arr; bs.excludedSourceFiles) ret[withSuffix("excludedSourceFiles", suffix)] = serializeToJson(arr);
  296. foreach (suffix, arr; bs.injectSourceFiles) ret[withSuffix("injectSourceFiles", suffix)] = serializeToJson(arr);
  297. foreach (suffix, arr; bs.copyFiles) ret[withSuffix("copyFiles", suffix)] = serializeToJson(arr);
  298. foreach (suffix, arr; bs.extraDependencyFiles) ret[withSuffix("extraDependencyFiles", suffix)] = serializeToJson(arr);
  299. foreach (suffix, arr; bs.versions) ret[withSuffix("versions", suffix)] = serializeToJson(arr);
  300. foreach (suffix, arr; bs.debugVersions) ret[withSuffix("debugVersions", suffix)] = serializeToJson(arr);
  301. foreach (suffix, arr; bs.versionFilters) ret[withSuffix("-versionFilters", suffix)] = serializeToJson(arr);
  302. foreach (suffix, arr; bs.debugVersionFilters) ret[withSuffix("-debugVersionFilters", suffix)] = serializeToJson(arr);
  303. foreach (suffix, arr; bs.importPaths) ret[withSuffix("importPaths", suffix)] = serializeToJson(arr);
  304. foreach (suffix, arr; bs.cImportPaths) ret[withSuffix("cImportPaths", suffix)] = serializeToJson(arr);
  305. foreach (suffix, arr; bs.stringImportPaths) ret[withSuffix("stringImportPaths", suffix)] = serializeToJson(arr);
  306. foreach (suffix, arr; bs.preGenerateCommands) ret[withSuffix("preGenerateCommands", suffix)] = serializeToJson(arr);
  307. foreach (suffix, arr; bs.postGenerateCommands) ret[withSuffix("postGenerateCommands", suffix)] = serializeToJson(arr);
  308. foreach (suffix, arr; bs.preBuildCommands) ret[withSuffix("preBuildCommands", suffix)] = serializeToJson(arr);
  309. foreach (suffix, arr; bs.postBuildCommands) ret[withSuffix("postBuildCommands", suffix)] = serializeToJson(arr);
  310. foreach (suffix, arr; bs.preRunCommands) ret[withSuffix("preRunCommands", suffix)] = serializeToJson(arr);
  311. foreach (suffix, arr; bs.postRunCommands) ret[withSuffix("postRunCommands", suffix)] = serializeToJson(arr);
  312. foreach (suffix, aa; bs.environments) ret[withSuffix("environments", suffix)] = serializeToJson(aa);
  313. foreach (suffix, aa; bs.buildEnvironments) ret[withSuffix("buildEnvironments", suffix)] = serializeToJson(aa);
  314. foreach (suffix, aa; bs.runEnvironments) ret[withSuffix("runEnvironments", suffix)] = serializeToJson(aa);
  315. foreach (suffix, aa; bs.preGenerateEnvironments) ret[withSuffix("preGenerateEnvironments", suffix)] = serializeToJson(aa);
  316. foreach (suffix, aa; bs.postGenerateEnvironments) ret[withSuffix("postGenerateEnvironments", suffix)] = serializeToJson(aa);
  317. foreach (suffix, aa; bs.preBuildEnvironments) ret[withSuffix("preBuildEnvironments", suffix)] = serializeToJson(aa);
  318. foreach (suffix, aa; bs.postBuildEnvironments) ret[withSuffix("postBuildEnvironments", suffix)] = serializeToJson(aa);
  319. foreach (suffix, aa; bs.preRunEnvironments) ret[withSuffix("preRunEnvironments", suffix)] = serializeToJson(aa);
  320. foreach (suffix, aa; bs.postRunEnvironments) ret[withSuffix("postRunEnvironments", suffix)] = serializeToJson(aa);
  321. foreach (suffix, arr; bs.buildRequirements) {
  322. string[] val;
  323. foreach (i; [EnumMembers!BuildRequirement])
  324. if (arr & i) val ~= to!string(i);
  325. ret[withSuffix("buildRequirements", suffix)] = serializeToJson(val);
  326. }
  327. foreach (suffix, arr; bs.buildOptions) {
  328. string[] val;
  329. foreach (i; [EnumMembers!BuildOption])
  330. if (arr & i) val ~= to!string(i);
  331. ret[withSuffix("buildOptions", suffix)] = serializeToJson(val);
  332. }
  333. return ret;
  334. }
  335.  
  336. private void parseJson(ref ToolchainRequirements tr, Json json)
  337. {
  338. foreach (string name, value; json)
  339. tr.addRequirement(name, value.get!string);
  340. }
  341.  
  342. private Json toJson(const scope ref ToolchainRequirements tr)
  343. {
  344. auto ret = Json.emptyObject;
  345. if (tr.dub != VersionRange.Any) ret["dub"] = serializeToJson(tr.dub);
  346. if (tr.frontend != VersionRange.Any) ret["frontend"] = serializeToJson(tr.frontend);
  347. if (tr.dmd != VersionRange.Any) ret["dmd"] = serializeToJson(tr.dmd);
  348. if (tr.ldc != VersionRange.Any) ret["ldc"] = serializeToJson(tr.ldc);
  349. if (tr.gdc != VersionRange.Any) ret["gdc"] = serializeToJson(tr.gdc);
  350. return ret;
  351. }
  352.  
  353. unittest {
  354. import std.string: strip, outdent;
  355. static immutable json = `
  356. {
  357. "name": "projectname",
  358. "environments": {
  359. "Var1": "env"
  360. },
  361. "buildEnvironments": {
  362. "Var2": "buildEnv"
  363. },
  364. "runEnvironments": {
  365. "Var3": "runEnv"
  366. },
  367. "preGenerateEnvironments": {
  368. "Var4": "preGenEnv"
  369. },
  370. "postGenerateEnvironments": {
  371. "Var5": "postGenEnv"
  372. },
  373. "preBuildEnvironments": {
  374. "Var6": "preBuildEnv"
  375. },
  376. "postBuildEnvironments": {
  377. "Var7": "postBuildEnv"
  378. },
  379. "preRunEnvironments": {
  380. "Var8": "preRunEnv"
  381. },
  382. "postRunEnvironments": {
  383. "Var9": "postRunEnv"
  384. }
  385. }
  386. `.strip.outdent;
  387. auto jsonValue = parseJsonString(json);
  388. PackageRecipe rec1;
  389. parseJson(rec1, jsonValue);
  390. PackageRecipe rec;
  391. // verify that all fields are serialized properly
  392. parseJson(rec, rec1.toJson());
  393.  
  394. assert(rec.name == "projectname");
  395. assert(rec.buildSettings.environments == ["": ["Var1": "env"]]);
  396. assert(rec.buildSettings.buildEnvironments == ["": ["Var2": "buildEnv"]]);
  397. assert(rec.buildSettings.runEnvironments == ["": ["Var3": "runEnv"]]);
  398. assert(rec.buildSettings.preGenerateEnvironments == ["": ["Var4": "preGenEnv"]]);
  399. assert(rec.buildSettings.postGenerateEnvironments == ["": ["Var5": "postGenEnv"]]);
  400. assert(rec.buildSettings.preBuildEnvironments == ["": ["Var6": "preBuildEnv"]]);
  401. assert(rec.buildSettings.postBuildEnvironments == ["": ["Var7": "postBuildEnv"]]);
  402. assert(rec.buildSettings.preRunEnvironments == ["": ["Var8": "preRunEnv"]]);
  403. assert(rec.buildSettings.postRunEnvironments == ["": ["Var9": "postRunEnv"]]);
  404. }