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, JSONType; 94 enforce!JSONException(json.type == JSONType.true_ || json.type == JSONType.false_, 95 "JSONValue is not a boolean"); 96 return json.type == JSONType.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, in string dubBinary) { 115 import std.json: JSONException; 116 import std.conv: text; 117 import std.algorithm: joiner, map, copy; 118 import std.range: empty; 119 import std.stdio: writeln; 120 import std.exception: enforce; 121 import std.process: environment, pipeProcess, Redirect, wait; 122 import std.array: join, appender; 123 124 if(verbose) 125 writeln("Running dub describe"); 126 127 const args = 128 [dubBinary, "describe", "-c", "unittest"] ~ 129 ("DC" in environment ? ["--compiler", environment["DC"]] : null); 130 auto pipes = pipeProcess(args, Redirect.stdout | Redirect.stderr); 131 scope(exit) wait(pipes.pid); // avoid zombies in all cases 132 string stdoutStr; 133 string stderrStr; 134 enum chunkSize = 4096; 135 pipes.stdout.byChunk(chunkSize).joiner 136 .map!"cast(immutable char)a".copy(appender(&stdoutStr)); 137 pipes.stderr.byChunk(chunkSize).joiner 138 .map!"cast(immutable char)a".copy(appender(&stderrStr)); 139 auto status = wait(pipes.pid); 140 auto allOutput = "stdout:\n" ~ stdoutStr ~ "\nstderr:\n" ~ stderrStr; 141 142 enforce(status == 0, text("Could not execute ", args.join(" "), 143 ":\n", allOutput)); 144 try { 145 return getDubInfo(stdoutStr); 146 } catch(JSONException e) { 147 throw new Exception(text("Could not parse the output of dub describe:\n", allOutput, "\n", e.toString)); 148 } 149 } 150 151 bool isDubProject() { 152 import std.file; 153 return "dub.sdl".exists || "dub.json".exists || "package.json".exists; 154 } 155 156 157 // set import paths from dub information 158 void dubify(ref from!"unit_threaded.runtime.runtime".Options options) { 159 160 import std.path: buildPath; 161 import std.algorithm: map, reduce; 162 import std.array: array; 163 164 if(!isDubProject) return; 165 166 auto dubInfo = getDubInfo(options.verbose, options.dubBinary); 167 options.includes = dubInfo.packages. 168 map!(a => a.importPaths.map!(b => buildPath(a.path, b)).array). 169 reduce!((a, b) => a ~ b).array; 170 options.files = dubInfo.packages[0].files; 171 }