1 module workspaced.com.dmd; 2 3 import core.thread; 4 import std.array; 5 import std.datetime; 6 import std.datetime.stopwatch : StopWatch; 7 import std.file; 8 import std.json; 9 import std.path; 10 import std.process; 11 import std.random; 12 13 import painlessjson; 14 15 import workspaced.api; 16 17 @component("dmd") 18 class DMDComponent : ComponentWrapper 19 { 20 mixin DefaultComponentWrapper; 21 22 /// Tries to compile a snippet of code with the import paths in the current directory. The arguments `-c -o-` are implicit. 23 /// The sync function may be used to prevent other measures from running while this is running. 24 /// Params: 25 /// cb = async callback 26 /// code = small code snippet to try to compile 27 /// dmdArguments = additional arguments to pass to dmd before file name 28 /// count = how often to compile (duration is divided by either this or less in case timeout is reached) 29 /// timeoutMsecs = when to abort compilation after, note that this will not abort mid-compilation but not do another iteration if this timeout has been reached. 30 /// Returns: [DMDMeasureReturn] containing logs from only the first compilation pass 31 Future!DMDMeasureReturn measure(scope const(char)[] code, 32 string[] dmdArguments = [], int count = 1, int timeoutMsecs = 5000) 33 { 34 return Future!DMDMeasureReturn.async(() => measureSync(code, dmdArguments, count, timeoutMsecs)); 35 } 36 37 /// ditto 38 DMDMeasureReturn measureSync(scope const(char)[] code, 39 string[] dmdArguments = [], int count = 1, int timeoutMsecs = 5000) 40 { 41 dmdArguments ~= ["-c", "-o-"]; 42 DMDMeasureReturn ret; 43 44 auto timeout = timeoutMsecs.msecs; 45 46 StopWatch sw; 47 48 int effective; 49 50 foreach (i; 0 .. count) 51 { 52 if (sw.peek >= timeout) 53 break; 54 string[] baseArgs = [path]; 55 foreach (path; importPaths) 56 baseArgs ~= "-I=" ~ path; 57 foreach (path; stringImportPaths) 58 baseArgs ~= "-J=" ~ path; 59 auto pipes = pipeProcess(baseArgs ~ dmdArguments ~ "-", 60 Redirect.stderrToStdout | Redirect.stdout | Redirect.stdin, null, 61 Config.none, instance.cwd); 62 pipes.stdin.write(code); 63 pipes.stdin.close(); 64 if (i == 0) 65 { 66 if (count == 0) 67 sw.start(); 68 ret.log = pipes.stdout.byLineCopy().array; 69 auto status = pipes.pid.wait(); 70 if (count == 0) 71 sw.stop(); 72 ret.success = status == 0; 73 ret.crash = status < 0; 74 } 75 else 76 { 77 if (count < 10 || i != 1) 78 sw.start(); 79 pipes.pid.wait(); 80 if (count < 10 || i != 1) 81 sw.stop(); 82 pipes.stdout.close(); 83 effective++; 84 } 85 if (!ret.success) 86 break; 87 } 88 89 ret.duration = sw.peek; 90 91 if (effective > 0) 92 ret.duration = ret.duration / effective; 93 94 return ret; 95 } 96 97 string path() @property @ignoredFunc 98 { 99 return config.get("dmd", "path", "dmd"); 100 } 101 } 102 103 /// 104 version (DigitalMars) unittest 105 { 106 scope backend = new WorkspaceD(); 107 auto workspace = makeTemporaryTestingWorkspace; 108 auto instance = backend.addInstance(workspace.directory); 109 backend.register!DMDComponent; 110 auto measure = backend.get!DMDComponent(workspace.directory) 111 .measure("import std.stdio;", null, 100).getBlocking; 112 assert(measure.success); 113 assert(measure.duration < 5.seconds); 114 } 115 116 /// 117 struct DMDMeasureReturn 118 { 119 /// true if dmd returned 0 120 bool success; 121 /// true if an ICE occured (segfault / negative return code) 122 bool crash; 123 /// compilation output 124 string[] log; 125 /// how long compilation took (serialized to msecs float in json) 126 Duration duration; 127 128 /// Converts a json object to [DMDMeasureReturn] 129 static DMDMeasureReturn fromJSON(JSONValue value) 130 { 131 DMDMeasureReturn ret; 132 if (auto success = "success" in value) 133 ret.success = success.type == JSONType.true_; 134 if (auto crash = "crash" in value) 135 ret.crash = crash.type == JSONType.true_; 136 if (auto log = "log" in value) 137 ret.log = (*log).fromJSON!(string[]); 138 if (auto duration = "duration" in value) 139 ret.duration = (cast(long)(duration.floating * 10_000)).hnsecs; 140 return ret; 141 } 142 143 /// Converts this object to a [JSONValue] 144 JSONValue toJSON() const 145 { 146 //dfmt off 147 return JSONValue([ 148 "success": JSONValue(success), 149 "crash": JSONValue(crash), 150 "log": log.toJSON, 151 "duration": JSONValue(duration.total!"hnsecs" / cast(double) 10_000) 152 ]); 153 //dfmt on 154 } 155 }