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 workspaced.api; 14 15 @component("dmd") 16 class DMDComponent : ComponentWrapper 17 { 18 mixin DefaultComponentWrapper; 19 20 /// Tries to compile a snippet of code with the import paths in the current directory. The arguments `-c -o-` are implicit. 21 /// The sync function may be used to prevent other measures from running while this is running. 22 /// Params: 23 /// cb = async callback 24 /// code = small code snippet to try to compile 25 /// dmdArguments = additional arguments to pass to dmd before file name 26 /// count = how often to compile (duration is divided by either this or less in case timeout is reached) 27 /// 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. 28 /// Returns: [DMDMeasureReturn] containing logs from only the first compilation pass 29 Future!DMDMeasureReturn measure(scope const(char)[] code, 30 string[] dmdArguments = [], int count = 1, int timeoutMsecs = 5000) 31 { 32 return typeof(return).async(() => measureSync(code, dmdArguments, count, timeoutMsecs)); 33 } 34 35 /// ditto 36 DMDMeasureReturn measureSync(scope const(char)[] code, 37 string[] dmdArguments = [], int count = 1, int timeoutMsecs = 5000) 38 { 39 dmdArguments ~= ["-c", "-o-"]; 40 DMDMeasureReturn ret; 41 42 auto timeout = timeoutMsecs.msecs; 43 44 StopWatch sw; 45 46 int effective; 47 48 foreach (i; 0 .. count) 49 { 50 if (sw.peek >= timeout) 51 break; 52 string[] baseArgs = [path]; 53 foreach (path; importPaths) 54 baseArgs ~= "-I=" ~ path; 55 foreach (path; stringImportPaths) 56 baseArgs ~= "-J=" ~ path; 57 auto pipes = pipeProcess(baseArgs ~ dmdArguments ~ "-", 58 Redirect.stderrToStdout | Redirect.stdout | Redirect.stdin, null, 59 Config.none, instance.cwd); 60 pipes.stdin.write(code); 61 pipes.stdin.close(); 62 if (i == 0) 63 { 64 if (count == 0) 65 sw.start(); 66 ret.log = pipes.stdout.byLineCopy().array; 67 auto status = pipes.pid.wait(); 68 if (count == 0) 69 sw.stop(); 70 ret.success = status == 0; 71 ret.crash = status < 0; 72 } 73 else 74 { 75 if (count < 10 || i != 1) 76 sw.start(); 77 pipes.pid.wait(); 78 if (count < 10 || i != 1) 79 sw.stop(); 80 pipes.stdout.close(); 81 effective++; 82 } 83 if (!ret.success) 84 break; 85 } 86 87 ret.duration = sw.peek; 88 89 if (effective > 0) 90 ret.duration = ret.duration / effective; 91 92 return ret; 93 } 94 95 string path() @property @ignoredFunc const 96 { 97 return config.get("dmd", "path", "dmd"); 98 } 99 } 100 101 /// 102 version (DigitalMars) unittest 103 { 104 scope backend = new WorkspaceD(); 105 auto workspace = makeTemporaryTestingWorkspace; 106 auto instance = backend.addInstance(workspace.directory); 107 backend.register!DMDComponent; 108 auto measure = backend.get!DMDComponent(workspace.directory) 109 .measure("import std.stdio;", null, 100).getBlocking; 110 assert(measure.success); 111 assert(measure.duration < 5.seconds); 112 } 113 114 /// 115 struct DMDMeasureReturn 116 { 117 /// true if dmd returned 0 118 bool success; 119 /// true if an ICE occured (segfault / negative return code) 120 bool crash; 121 /// compilation output 122 string[] log; 123 /// how long compilation took (serialized to msecs float in json) 124 Duration duration; 125 }