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 }