1 module unit_threaded.dub; 2 3 import unit_threaded.runtime; 4 import std.json; 5 import std.algorithm; 6 import std.array; 7 import std.process; 8 import std.exception; 9 import std.conv; 10 import std.stdio; 11 12 13 struct DubPackage { 14 string name; 15 string path; 16 string mainSourceFile; 17 string targetFileName; 18 string[] flags; 19 string[] importPaths; 20 string[] stringImportPaths; 21 string[] files; 22 string targetType; 23 string[] versions; 24 string[] dependencies; 25 string[] libs; 26 bool active; 27 } 28 29 struct DubInfo { 30 DubPackage[] packages; 31 } 32 33 DubInfo getDubInfo(string jsonString) @trusted { 34 auto json = parseJSON(jsonString); 35 auto packages = json.byKey("packages").array; 36 return DubInfo(packages. 37 map!(a => DubPackage(a.byKey("name").str, 38 a.byKey("path").str, 39 a.getOptional("mainSourceFile"), 40 a.getOptional("targetFileName"), 41 a.byKey("dflags").jsonValueToStrings, 42 a.byKey("importPaths").jsonValueToStrings, 43 a.byKey("stringImportPaths").jsonValueToStrings, 44 a.byKey("files").jsonValueToFiles, 45 a.getOptional("targetType"), 46 a.getOptionalList("versions"), 47 a.getOptionalList("dependencies"), 48 a.getOptionalList("libs"), 49 a.byOptionalKey("active", true), //true for backwards compatibility 50 )). 51 filter!(a => a.active). 52 array); 53 } 54 55 private string[] jsonValueToFiles(JSONValue files) @trusted { 56 import std.array; 57 58 return files.array. 59 filter!(a => ("type" in a && a.byKey("type").str == "source") || 60 ("role" in a && a.byKey("role").str == "source") || 61 ("type" !in a && "role" !in a)). 62 map!(a => a.byKey("path").str). 63 array; 64 } 65 66 private string[] jsonValueToStrings(JSONValue json) @trusted { 67 return json.array.map!(a => a.str).array; 68 } 69 70 71 private auto byKey(JSONValue json, in string key) @trusted { 72 return json.object[key]; 73 } 74 75 private auto byOptionalKey(JSONValue json, in string key, bool def) { 76 import std.conv: to; 77 auto value = json.object; 78 return key in value ? value[key].boolean : def; 79 } 80 81 //std.json has no conversion to bool 82 private bool boolean(JSONValue json) @trusted { 83 import std.exception: enforce; 84 enforce!JSONException(json.type == JSON_TYPE.TRUE || json.type == JSON_TYPE.FALSE, 85 "JSONValue is not a boolean"); 86 return json.type == JSON_TYPE.TRUE; 87 } 88 89 private string getOptional(JSONValue json, in string key) @trusted { 90 auto aa = json.object; 91 return key in aa ? aa[key].str : ""; 92 } 93 94 private string[] getOptionalList(JSONValue json, in string key) @trusted { 95 auto aa = json.object; 96 return key in aa ? aa[key].jsonValueToStrings : []; 97 } 98 99 100 DubInfo getDubInfo(in bool verbose) { 101 import core.exception; 102 103 if(verbose) 104 writeln("Running dub describe"); 105 106 immutable args = ["dub", "describe", "-c", "unittest"]; 107 immutable res = execute(args); 108 enforce(res.status == 0, text("Could not execute ", args.join(" "), ":\n", res.output)); 109 try { 110 return getDubInfo(res.output.find("{")); 111 } catch(RangeError e) { 112 throw new Exception(text("Could not parse the output of dub describe:\n", res.output, "\n", e.toString)); 113 } 114 } 115 116 bool isDubProject() { 117 import std.file; 118 return "dub.sdl".exists || "dub.json".exists || "package.json".exists; 119 } 120 121 122 // set import paths from dub information 123 void dubify(ref Options options) { 124 if(!isDubProject) return; 125 126 auto dubInfo = getDubInfo(options.verbose); 127 options.includes = dubInfo.packages. 128 map!(a => a.importPaths.map!(b => buildPath(a.path, b)).array). 129 reduce!((a, b) => a ~ b).array; 130 options.files = dubInfo.packages[0].files; 131 }