1 /** 2 Code to parse the output from `dub describe` and generate the main 3 test file automatically. 4 */ 5 module unit_threaded.runtime.dub; 6 7 import unit_threaded.from; 8 9 10 struct DubPackage { 11 string name; 12 string path; 13 string mainSourceFile; 14 string targetFileName; 15 string[] flags; 16 string[] importPaths; 17 string[] stringImportPaths; 18 string[] files; 19 string targetType; 20 string[] versions; 21 string[] dependencies; 22 string[] libs; 23 bool active; 24 } 25 26 struct DubInfo { 27 DubPackage[] packages; 28 } 29 30 DubInfo getDubInfo(string jsonString) @trusted { 31 import std.json: parseJSON; 32 import std.algorithm: map, filter; 33 import std.array: array; 34 35 auto json = parseJSON(jsonString); 36 auto packages = json.byKey("packages").array; 37 return DubInfo(packages. 38 map!(a => DubPackage(a.byKey("name").str, 39 a.byKey("path").str, 40 a.getOptional("mainSourceFile"), 41 a.getOptional("targetFileName"), 42 a.byKey("dflags").jsonValueToStrings, 43 a.byKey("importPaths").jsonValueToStrings, 44 a.byKey("stringImportPaths").jsonValueToStrings, 45 a.byKey("files").jsonValueToFiles, 46 a.getOptional("targetType"), 47 a.getOptionalList("versions"), 48 a.getOptionalList("dependencies"), 49 a.getOptionalList("libs"), 50 a.byOptionalKey("active", true), //true for backwards compatibility 51 )). 52 filter!(a => a.active). 53 array); 54 } 55 56 private string[] jsonValueToFiles(from!"std.json".JSONValue files) @trusted { 57 import std.algorithm: map, filter; 58 import std.array: array; 59 60 return files.array. 61 filter!(a => ("type" in a && a.byKey("type").str == "source") || 62 ("role" in a && a.byKey("role").str == "source") || 63 ("type" !in a && "role" !in a)). 64 map!(a => a.byKey("path").str). 65 array; 66 } 67 68 private string[] jsonValueToStrings(from!"std.json".JSONValue json) @trusted { 69 import std.algorithm: map, filter; 70 import std.array: array; 71 72 return json.array.map!(a => a.str).array; 73 } 74 75 76 private auto byKey(from!"std.json".JSONValue json, in string key) @trusted { 77 import std.json: JSONException; 78 if (auto p = key in json.object) 79 return *p; 80 else throw new JSONException("\"" ~ key ~ "\" not found"); 81 } 82 83 private auto byOptionalKey(from!"std.json".JSONValue json, in string key, bool def) { 84 if (auto p = key in json.object) 85 return (*p).boolean; 86 else 87 return def; 88 } 89 90 //std.json has no conversion to bool 91 private bool boolean(from!"std.json".JSONValue json) @trusted { 92 import std.exception: enforce; 93 import std.json: JSONException, JSON_TYPE; 94 enforce!JSONException(json.type == JSON_TYPE.TRUE || json.type == JSON_TYPE.FALSE, 95 "JSONValue is not a boolean"); 96 return json.type == JSON_TYPE.TRUE; 97 } 98 99 private string getOptional(from!"std.json".JSONValue json, in string key) @trusted { 100 if (auto p = key in json.object) 101 return p.str; 102 else 103 return ""; 104 } 105 106 private string[] getOptionalList(from!"std.json".JSONValue json, in string key) @trusted { 107 if (auto p = key in json.object) 108 return (*p).jsonValueToStrings; 109 else 110 return []; 111 } 112 113 114 DubInfo getDubInfo(in bool verbose) { 115 import std.json: JSONException; 116 import std.conv: text; 117 import std.algorithm: joiner, map, copy; 118 import std.stdio: writeln; 119 import std.exception: enforce; 120 import std.process: pipeProcess, Redirect, wait; 121 import std.array: join, appender; 122 123 if(verbose) 124 writeln("Running dub describe"); 125 126 immutable args = ["dub", "describe", "-c", "unittest"]; 127 auto pipes = pipeProcess(args, Redirect.stdout | Redirect.stderr); 128 scope(exit) wait(pipes.pid); // avoid zombies in all cases 129 string stdoutStr; 130 string stderrStr; 131 enum chunkSize = 4096; 132 pipes.stdout.byChunk(chunkSize).joiner 133 .map!"cast(immutable char)a".copy(appender(&stdoutStr)); 134 pipes.stderr.byChunk(chunkSize).joiner 135 .map!"cast(immutable char)a".copy(appender(&stderrStr)); 136 auto status = wait(pipes.pid); 137 auto allOutput = "stdout:\n" ~ stdoutStr ~ "\nstderr:\n" ~ stderrStr; 138 139 enforce(status == 0, text("Could not execute ", args.join(" "), 140 ":\n", allOutput)); 141 try { 142 return getDubInfo(stdoutStr); 143 } catch(JSONException e) { 144 throw new Exception(text("Could not parse the output of dub describe:\n", allOutput, "\n", e.toString)); 145 } 146 } 147 148 bool isDubProject() { 149 import std.file; 150 return "dub.sdl".exists || "dub.json".exists || "package.json".exists; 151 } 152 153 154 // set import paths from dub information 155 void dubify(ref from!"unit_threaded.runtime.runtime".Options options) { 156 157 import std.path: buildPath; 158 import std.algorithm: map, reduce; 159 import std.array: array; 160 161 if(!isDubProject) return; 162 163 auto dubInfo = getDubInfo(options.verbose); 164 options.includes = dubInfo.packages. 165 map!(a => a.importPaths.map!(b => buildPath(a.path, b)).array). 166 reduce!((a, b) => a ~ b).array; 167 options.files = dubInfo.packages[0].files; 168 }