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 }