1 module dlangide.builders.builder; 2 3 import dlangui.core.logger; 4 import dlangide.workspace.project; 5 import dlangide.workspace.workspace; 6 import dlangide.ui.outputpanel; 7 import dlangide.builders.extprocess; 8 import dlangui.widgets.appframe; 9 import std.algorithm; 10 import std.array; 11 import core.thread; 12 import std.string; 13 import std.conv; 14 15 alias BuildResultListener = void delegate(int); 16 17 class Builder : BackgroundOperationWatcher { 18 protected Project _project; 19 protected ExternalProcess _extprocess; 20 protected OutputPanel _log; 21 protected ProtectedTextStorage _box; 22 protected ProjectConfiguration _projectConfig; 23 protected BuildConfiguration _buildConfig; 24 protected BuildOperation _buildOp; 25 protected string _executable; 26 protected string _additionalParams; 27 protected BuildResultListener _listener; 28 protected int _exitCode = int.min; 29 protected string _toolchain; 30 protected string _arch; 31 32 @property Project project() { return _project; } 33 @property void project(Project p) { _project = p; } 34 35 this(AppFrame frame, Project project, OutputPanel log, ProjectConfiguration projectConfig, BuildConfiguration buildConfig, 36 BuildOperation buildOp, 37 string dubExecutable, 38 string dubAdditionalParams, 39 string toolchain = null, 40 string arch = null, 41 BuildResultListener listener = null) { 42 super(frame); 43 _listener = listener; 44 _projectConfig = projectConfig; 45 _buildConfig = buildConfig; 46 _buildOp = buildOp; 47 _executable = dubExecutable.empty ? "dub" : dubExecutable; 48 _additionalParams = dubAdditionalParams; 49 _project = project; 50 _log = log; 51 _toolchain = toolchain; 52 _arch = arch; 53 _extprocess = new ExternalProcess(); 54 _box = new ProtectedTextStorage(); 55 } 56 57 /// log lines 58 void pollText() { 59 dstring text = _box.readText(); 60 if (text.length) { 61 _log.appendText(null, text); 62 } 63 } 64 65 /// returns icon of background operation to show in status line 66 override @property string icon() { return "folder"; } 67 /// update background operation status 68 override void update() { 69 scope(exit)pollText(); 70 ExternalProcessState state = _extprocess.state; 71 if (state == ExternalProcessState.None) { 72 import dlangui.core.files; 73 string exepath = findExecutablePath(_executable); 74 if (!exepath) { 75 _finished = true; 76 destroy(_extprocess); 77 _extprocess = null; 78 return; 79 } 80 81 _log.clear(); 82 char[] program = exepath.dup; 83 char[][] params; 84 char[] dir = _project.dir.dup; 85 86 { 87 // dub 88 if (_buildOp == BuildOperation.Build || _buildOp == BuildOperation.Rebuild) { 89 params ~= "build".dup; 90 if (_buildOp == BuildOperation.Rebuild) { 91 params ~= "--force".dup; 92 } 93 if (!_arch.empty) 94 params ~= ("--arch=" ~ _arch).dup; 95 if (!_toolchain.empty) 96 params ~= ("--compiler=" ~ _toolchain).dup; 97 params ~= "--build-mode=allAtOnce".dup; 98 } else if (_buildOp == BuildOperation.Clean) { 99 params ~= "clean".dup; 100 } else if (_buildOp == BuildOperation.Run) { 101 if (_projectConfig.type == ProjectConfiguration.Type.Library) { 102 params ~= "test".dup; 103 } else { 104 params ~= "run".dup; 105 } 106 } else if (_buildOp == BuildOperation.Upgrade) { 107 params ~= "upgrade".dup; 108 params ~= "--force-remove".dup; 109 import std.path; 110 import std.file; 111 string projectFile = project.filename; 112 string selectionsFile = projectFile.stripExtension ~ ".selections.json"; 113 if (selectionsFile.exists && selectionsFile.isFile) { 114 Log.i("Removing file ", selectionsFile); 115 remove(selectionsFile); 116 } 117 } 118 119 if (_buildOp != BuildOperation.Clean && _buildOp != BuildOperation.Upgrade) { 120 switch (_buildConfig) { 121 default: 122 case BuildConfiguration.Debug: 123 params ~= "--build=debug".dup; 124 break; 125 case BuildConfiguration.Release: 126 params ~= "--build=release".dup; 127 break; 128 case BuildConfiguration.Unittest: 129 params ~= "--build=unittest".dup; 130 break; 131 } 132 if (!_additionalParams.empty) 133 params ~= _additionalParams.dup; 134 } 135 136 if(_projectConfig.name != ProjectConfiguration.DEFAULT_NAME) { 137 params ~= "--config=".dup ~ _projectConfig.name; 138 } 139 } 140 141 auto text = "Running (in " ~ dir ~ "): " ~ program ~ " " ~ params.join(' ') ~ "\n"; 142 _box.writeText(to!dstring(text)); 143 state = _extprocess.run(program, params, dir, _box, null); 144 if (state != ExternalProcessState.Running) { 145 _box.writeText("Failed to run builder tool\n"d); 146 _finished = true; 147 destroy(_extprocess); 148 _extprocess = null; 149 return; 150 } 151 } 152 state = _extprocess.poll(); 153 if (state == ExternalProcessState.Stopped) { 154 _exitCode = _extprocess.result; 155 _box.writeText("Builder finished with result "d ~ to!dstring(_extprocess.result) ~ "\n"d); 156 _finished = true; 157 return; 158 } 159 if (_cancelRequested) { 160 _extprocess.kill(); 161 _extprocess.wait(); 162 _finished = true; 163 return; 164 } 165 super.update(); 166 } 167 override void removing() { 168 super.removing(); 169 if (_exitCode != int.min && _listener) 170 _listener(_exitCode); 171 } 172 }