1 module reggae.dub.json; 2 3 import reggae.dub.info; 4 import reggae.build; 5 import std.json; 6 import std.algorithm: map, filter; 7 8 9 DubInfo getDubInfo(in string origString) @trusted { 10 11 import std.string: indexOf; 12 import std.array; 13 import core.exception: RangeError; 14 15 string nextOpenCurly(string str) { 16 return str[str.indexOf("{") .. $]; 17 } 18 19 try { 20 21 // the output might contain non-JSON at the beginning in stderr 22 auto jsonString = nextOpenCurly(origString); 23 24 for(; ; jsonString = nextOpenCurly(jsonString[1..$])) { 25 auto json = parseJSON(jsonString); 26 27 bool hasTargets() { 28 try 29 return ("targets" in json.object) !is null; 30 catch(JSONException ex) 31 return false; 32 } 33 34 if(!hasTargets) { 35 continue; 36 } 37 38 auto targets = json.byKey("targets").array; 39 auto info = DubInfo(targets 40 .map!((a) { 41 auto bs = a.object["buildSettings"]; 42 return DubPackage( 43 bs.byKey("targetName").str, 44 bs.byKey("targetPath").str, 45 bs.getOptional("mainSourceFile"), 46 bs.getOptional("targetName"), 47 bs.byKey("dflags").jsonValueToStrings, 48 bs.byKey("lflags").jsonValueToStrings, 49 bs.byKey("importPaths").jsonValueToStrings, 50 bs.byKey("stringImportPaths").jsonValueToStrings, 51 bs.byKey("sourceFiles").jsonValueToStrings, 52 bs.getOptionalEnum!TargetType("targetType"), 53 bs.getOptionalList("versions"), 54 a.getOptionalList("dependencies"), 55 bs.getOptionalList("libs"), 56 true, // backwards compatibility (active) 57 bs.getOptionalList("preBuildCommands"), 58 bs.getOptionalList("postBuildCommands"), 59 ); 60 }) 61 .filter!(a => a.active) 62 .array); 63 64 65 66 // in dub.json/dub.sdl, $PACKAGE_DIR is a variable that refers to the root 67 // of the dub package 68 void resolvePackageDir(in DubPackage dubPackage, ref string str) { 69 str = str.replace("$PACKAGE_DIR", dubPackage.path); 70 } 71 72 foreach(ref dubPackage; info.packages) { 73 foreach(ref member; dubPackage.tupleof) { 74 75 static if(is(typeof(member) == string)) { 76 resolvePackageDir(dubPackage, member); 77 } else static if(is(typeof(member) == string[])) { 78 foreach(ref elt; member) 79 resolvePackageDir(dubPackage, elt); 80 } 81 } 82 } 83 84 return info; 85 } 86 } catch(RangeError e) { 87 import std.stdio; 88 stderr.writeln("Could not parse the output of dub describe:\n", origString); 89 throw e; 90 } 91 } 92 93 94 private string[] jsonValueToFiles(JSONValue files) @trusted { 95 import std.array; 96 97 return files.array. 98 filter!(a => ("type" in a && a.byKey("type").str == "source") || 99 ("role" in a && a.byKey("role").str == "source") || 100 ("type" !in a && "role" !in a)). 101 map!(a => a.byKey("path").str). 102 array; 103 } 104 105 private string[] jsonValueToStrings(JSONValue json) @trusted { 106 import std.array; 107 return json.array.map!(a => a.str).array; 108 } 109 110 111 private JSONValue byKey(JSONValue json, in string key) @trusted { 112 import core.exception: RangeError; 113 try 114 return json.object[key]; 115 catch(RangeError e) { 116 throw new Exception("Could not find key " ~ key); 117 } 118 } 119 120 private bool byOptionalKey(JSONValue json, in string key, bool def) { 121 import std.conv: to; 122 auto value = json.object; 123 return key in value ? value[key].boolean : def; 124 } 125 126 //std.json has no conversion to bool 127 private bool boolean(JSONValue json) @trusted { 128 import std.exception: enforce; 129 enforce!JSONException(json.type == JSON_TYPE.TRUE || json.type == JSON_TYPE.FALSE, 130 "JSONValue is not a boolean"); 131 return json.type == JSON_TYPE.TRUE; 132 } 133 134 private string getOptional(JSONValue json, in string key) @trusted { 135 auto aa = json.object; 136 return key in aa ? aa[key].str : ""; 137 } 138 139 private T getOptionalEnum(T)(JSONValue json, in string key) @trusted { 140 auto aa = json.object; 141 return key in aa ? cast(T)aa[key].integer : T.init; 142 } 143 144 private string[] getOptionalList(JSONValue json, in string key) @trusted { 145 auto aa = json.object; 146 return key in aa ? aa[key].jsonValueToStrings : []; 147 }