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