1 /****************************************************************************
2 **
3 ** Copyright (C) 2020 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of Qt Creator.
7 **
8 ** Commercial License Usage
9 ** Licensees holding valid commercial Qt licenses may use this file in
10 ** accordance with the commercial license agreement provided with the
11 ** Software or, alternatively, in accordance with the terms contained in
12 ** a written agreement between you and The Qt Company. For licensing terms
13 ** and conditions see https://www.qt.io/terms-conditions. For further
14 ** information use the contact form at https://www.qt.io/contact-us.
15 **
16 ** GNU General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU
18 ** General Public License version 3 as published by the Free Software
19 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
20 ** included in the packaging of this file. Please review the following
21 ** information to ensure the GNU General Public License requirements will
22 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
23 **
24 ****************************************************************************/
25 
26 #include "buildconsolebuildstep.h"
27 
28 #include "commandbuilderaspect.h"
29 #include "incredibuildconstants.h"
30 
31 #include <projectexplorer/abstractprocessstep.h>
32 #include <projectexplorer/buildconfiguration.h>
33 #include <projectexplorer/gnumakeparser.h>
34 #include <projectexplorer/kit.h>
35 #include <projectexplorer/processparameters.h>
36 #include <projectexplorer/projectexplorerconstants.h>
37 #include <projectexplorer/target.h>
38 
39 #include <utils/aspects.h>
40 #include <utils/environment.h>
41 
42 using namespace ProjectExplorer;
43 using namespace Utils;
44 
45 namespace IncrediBuild {
46 namespace Internal {
47 
48 namespace Constants {
49 const QLatin1String BUILDCONSOLE_AVOIDLOCAL("IncrediBuild.BuildConsole.AvoidLocal");
50 const QLatin1String BUILDCONSOLE_PROFILEXML("IncrediBuild.BuildConsole.ProfileXml");
51 const QLatin1String BUILDCONSOLE_MAXCPU("IncrediBuild.BuildConsole.MaxCpu");
52 const QLatin1String BUILDCONSOLE_MAXWINVER("IncrediBuild.BuildConsole.MaxWinVer");
53 const QLatin1String BUILDCONSOLE_MINWINVER("IncrediBuild.BuildConsole.MinWinVer");
54 const QLatin1String BUILDCONSOLE_TITLE("IncrediBuild.BuildConsole.Title");
55 const QLatin1String BUILDCONSOLE_MONFILE("IncrediBuild.BuildConsole.MonFile");
56 const QLatin1String BUILDCONSOLE_SUPPRESSSTDOUT("IncrediBuild.BuildConsole.SuppressStdOut");
57 const QLatin1String BUILDCONSOLE_LOGFILE("IncrediBuild.BuildConsole.LogFile");
58 const QLatin1String BUILDCONSOLE_SHOWCMD("IncrediBuild.BuildConsole.ShowCmd");
59 const QLatin1String BUILDCONSOLE_SHOWAGENTS("IncrediBuild.BuildConsole.ShowAgents");
60 const QLatin1String BUILDCONSOLE_SHOWTIME("IncrediBuild.BuildConsole.ShowTime");
61 const QLatin1String BUILDCONSOLE_HIDEHEADER("IncrediBuild.BuildConsole.HideHeader");
62 const QLatin1String BUILDCONSOLE_LOGLEVEL("IncrediBuild.BuildConsole.LogLevel");
63 const QLatin1String BUILDCONSOLE_SETENV("IncrediBuild.BuildConsole.SetEnv");
64 const QLatin1String BUILDCONSOLE_STOPONERROR("IncrediBuild.BuildConsole.StopOnError");
65 const QLatin1String BUILDCONSOLE_ADDITIONALARGUMENTS("IncrediBuild.BuildConsole.AdditionalArguments");
66 const QLatin1String BUILDCONSOLE_OPENMONITOR("IncrediBuild.BuildConsole.OpenMonitor");
67 const QLatin1String BUILDCONSOLE_KEEPJOBNUM("IncrediBuild.BuildConsole.KeepJobNum");
68 const QLatin1String BUILDCONSOLE_COMMANDBUILDER("IncrediBuild.BuildConsole.CommandBuilder");
69 }
70 
normalizeWinVerArgument(QString winVer)71 static QString normalizeWinVerArgument(QString winVer)
72 {
73     winVer.remove("Windows ");
74     winVer.remove("Server ");
75     return winVer.toUpper();
76 }
77 
supportedWindowsVersions()78 const QStringList &supportedWindowsVersions()
79 {
80     static QStringList list({QString(),
81                              "Windows 7",
82                              "Windows 8",
83                              "Windows 10",
84                              "Windows Vista",
85                              "Windows XP",
86                              "Windows Server 2003",
87                              "Windows Server 2008",
88                              "Windows Server 2012"});
89     return list;
90 }
91 
92 class BuildConsoleBuildStep : public AbstractProcessStep
93 {
94     Q_DECLARE_TR_FUNCTIONS(IncrediBuild::Internal::BuildConsoleBuildStep)
95 
96 public:
97     BuildConsoleBuildStep(BuildStepList *buildStepList, Id id);
98 
99     void setupOutputFormatter(OutputFormatter *formatter) final;
100 };
101 
BuildConsoleBuildStep(BuildStepList * buildStepList,Id id)102 BuildConsoleBuildStep::BuildConsoleBuildStep(BuildStepList *buildStepList, Id id)
103     : AbstractProcessStep(buildStepList, id)
104 {
105     setDisplayName(tr("IncrediBuild for Windows"));
106 
107     addAspect<TextDisplay>("<b>" + tr("Target and Configuration"));
108 
109     auto commandBuilder = addAspect<CommandBuilderAspect>(this);
110     commandBuilder->setSettingsKey(Constants::BUILDCONSOLE_COMMANDBUILDER);
111 
112     addAspect<TextDisplay>("<i>" + tr("Enter the appropriate arguments to your build command."));
113     addAspect<TextDisplay>("<i>" + tr("Make sure the build command's multi-job "
114                                       "parameter value is large enough "
115                                       "(such as -j200 for the JOM or Make build tools)"));
116 
117     auto keepJobNum = addAspect<BoolAspect>();
118     keepJobNum->setSettingsKey(Constants::BUILDCONSOLE_KEEPJOBNUM);
119     keepJobNum->setLabel(tr("Keep original jobs number:"));
120     keepJobNum->setToolTip(tr("Forces IncrediBuild to not override the -j command line switch, "
121                               "that controls the number of parallel spawned tasks. The default "
122                               "IncrediBuild behavior is to set it to 200."));
123 
124     addAspect<TextDisplay>("<b>" + tr("IncrediBuild Distribution Control"));
125 
126     auto profileXml = addAspect<StringAspect>();
127     profileXml->setSettingsKey(Constants::BUILDCONSOLE_PROFILEXML);
128     profileXml->setLabelText(tr("Profile.xml:"));
129     profileXml->setDisplayStyle(StringAspect::PathChooserDisplay);
130     profileXml->setExpectedKind(PathChooser::Kind::File);
131     profileXml->setBaseFileName(FilePath::fromString(PathChooser::homePath()));
132     profileXml->setHistoryCompleter("IncrediBuild.BuildConsole.ProfileXml.History");
133     profileXml->setToolTip(tr("Defines how Automatic "
134                               "Interception Interface should handle the various processes "
135                               "involved in a distributed job. It is not necessary for "
136                               "\"Visual Studio\" or \"Make and Build tools\" builds, "
137                               "but can be used to provide configuration options if those "
138                               "builds use additional processes that are not included in "
139                               "those packages. It is required to configure distributable "
140                               "processes in \"Dev Tools\" builds."));
141 
142     auto avoidLocal = addAspect<BoolAspect>();
143     avoidLocal->setSettingsKey(Constants::BUILDCONSOLE_AVOIDLOCAL);
144     avoidLocal->setLabel(tr("Avoid local task execution:"));
145     avoidLocal->setToolTip(tr("Overrides the Agent Settings dialog Avoid task execution on local "
146                               "machine when possible option. This allows to free more resources "
147                               "on the initiator machine and could be beneficial to distribution "
148                               "in scenarios where the initiating machine is bottlenecking the "
149                               "build with High CPU usage."));
150 
151     auto maxCpu = addAspect<IntegerAspect>();
152     maxCpu->setSettingsKey(Constants::BUILDCONSOLE_MAXCPU);
153     maxCpu->setToolTip(tr("Determines the maximum number of CPU cores that can be used in a "
154                           "build, regardless of the number of available Agents. "
155                           "It takes into account both local and remote cores, even if the "
156                           "Avoid Task Execution on Local Machine option is selected."));
157     maxCpu->setLabel(tr("Maximum CPUs to utilize in the build:"));
158     maxCpu->setRange(0, 65536);
159 
160     auto maxWinVer = addAspect<SelectionAspect>();
161     maxWinVer->setSettingsKey(Constants::BUILDCONSOLE_MAXWINVER);
162     maxWinVer->setDisplayName(tr("Newest allowed helper machine OS:"));
163     maxWinVer->setDisplayStyle(SelectionAspect::DisplayStyle::ComboBox);
164     maxWinVer->setToolTip(tr("Specifies the newest operating system installed on a helper "
165                              "machine to be allowed to participate as helper in the build."));
166     for (const QString &version : supportedWindowsVersions())
167         maxWinVer->addOption(version);
168 
169     auto minWinVer = addAspect<SelectionAspect>();
170     minWinVer->setSettingsKey(Constants::BUILDCONSOLE_MINWINVER);
171     minWinVer->setDisplayName(tr("Oldest allowed helper machine OS:"));
172     minWinVer->setDisplayStyle(SelectionAspect::DisplayStyle::ComboBox);
173     minWinVer->setToolTip(tr("Specifies the oldest operating system installed on a helper "
174                              "machine to be allowed to participate as helper in the build."));
175     for (const QString &version : supportedWindowsVersions())
176         minWinVer->addOption(version);
177 
178     addAspect<TextDisplay>("<b>" + tr("Output and Logging"));
179 
180     auto title = addAspect<StringAspect>();
181     title->setSettingsKey(Constants::BUILDCONSOLE_TITLE);
182     title->setLabelText(tr("Build title:"));
183     title->setDisplayStyle(StringAspect::LineEditDisplay);
184     title->setToolTip(tr("Specifies a custom header line which will be displayed in the "
185                          "beginning of the build output text. This title will also be used "
186                          "for the Build History and Build Monitor displays."));
187 
188     auto monFile = addAspect<StringAspect>();
189     monFile->setSettingsKey(Constants::BUILDCONSOLE_MONFILE);
190     monFile->setLabelText(tr("Save IncrediBuild monitor file:"));
191     monFile->setDisplayStyle(StringAspect::PathChooserDisplay);
192     monFile->setExpectedKind(PathChooser::Kind::Any);
193     monFile->setBaseFileName(FilePath::fromString(PathChooser::homePath()));
194     monFile->setHistoryCompleter(QLatin1String("IncrediBuild.BuildConsole.MonFile.History"));
195     monFile->setToolTip(tr("Writes a copy of the build progress file (.ib_mon) to the specified "
196                            "location. If only a folder name is given, a generated GUID will serve "
197                            "as the file name. The full path of the saved Build Monitor will be "
198                            "written to the end of the build output."));
199 
200     auto suppressStdOut = addAspect<BoolAspect>();
201     suppressStdOut->setSettingsKey(Constants::BUILDCONSOLE_SUPPRESSSTDOUT);
202     suppressStdOut->setLabel(tr("Suppress STDOUT:"));
203     suppressStdOut->setToolTip(tr("Does not write anything to the standard output."));
204 
205     auto logFile = addAspect<StringAspect>();
206     logFile->setSettingsKey(Constants::BUILDCONSOLE_LOGFILE);
207     logFile->setLabelText(tr("Output Log file:"));
208     logFile->setDisplayStyle(StringAspect::PathChooserDisplay);
209     logFile->setExpectedKind(PathChooser::Kind::SaveFile);
210     logFile->setBaseFileName(FilePath::fromString(PathChooser::homePath()));
211     logFile->setHistoryCompleter(QLatin1String("IncrediBuild.BuildConsole.LogFile.History"));
212     logFile->setToolTip(tr("Writes build output to a file."));
213 
214     auto showCmd = addAspect<BoolAspect>();
215     showCmd->setSettingsKey(Constants::BUILDCONSOLE_SHOWCMD);
216     showCmd->setLabel(tr("Show Commands in output:"));
217     showCmd->setToolTip(tr("Shows, for each file built, the command-line used by IncrediBuild "
218                            "to build the file."));
219 
220     auto showAgents = addAspect<BoolAspect>();
221     showAgents->setSettingsKey(Constants::BUILDCONSOLE_SHOWAGENTS);
222     showAgents->setLabel(tr("Show Agents in output:"));
223     showAgents->setToolTip(tr("Shows the Agent used to build each file."));
224 
225     auto showTime = addAspect<BoolAspect>();
226     showTime->setSettingsKey(Constants::BUILDCONSOLE_SHOWTIME);
227     showTime->setLabel(tr("Show Time in output:"));
228     showTime->setToolTip(tr("Shows the Start and Finish time for each file built."));
229 
230     auto hideHeader = addAspect<BoolAspect>();
231     hideHeader->setSettingsKey(Constants::BUILDCONSOLE_HIDEHEADER);
232     hideHeader->setLabel(tr("Hide IncrediBuild Header in output:"));
233     hideHeader->setToolTip(tr("Suppresses IncrediBuild's header in the build output"));
234 
235     auto logLevel = addAspect<SelectionAspect>();
236     logLevel->setSettingsKey(Constants::BUILDCONSOLE_LOGLEVEL);
237     logLevel->setDisplayName(tr("Internal IncrediBuild logging level:"));
238     logLevel->setDisplayStyle(SelectionAspect::DisplayStyle::ComboBox);
239     logLevel->addOption(QString());
240     logLevel->addOption("Minimal");
241     logLevel->addOption("Extended");
242     logLevel->addOption("Detailed");
243     logLevel->setToolTip(tr("Overrides the internal Incredibuild logging level for this build. "
244                             "Does not affect output or any user accessible logging. Used mainly "
245                             "to troubleshoot issues with the help of IncrediBuild support"));
246 
247     addAspect<TextDisplay>("<b>" + tr("Miscellaneous"));
248 
249     auto setEnv = addAspect<StringAspect>();
250     setEnv->setSettingsKey(Constants::BUILDCONSOLE_SETENV);
251     setEnv->setLabelText(tr("Set an Environment Variable:"));
252     setEnv->setDisplayStyle(StringAspect::LineEditDisplay);
253     setEnv->setToolTip(tr("Sets or overrides environment variables for the context of the build."));
254 
255     auto stopOnError = addAspect<BoolAspect>();
256     stopOnError->setSettingsKey(Constants::BUILDCONSOLE_STOPONERROR);
257     stopOnError->setLabel(tr("Stop on errors:"));
258     stopOnError->setToolTip(tr("When specified, the execution will stop as soon as an error "
259                                "is encountered. This is the default behavior in "
260                                "\"Visual Studio\" builds, but not the default for "
261                                "\"Make and Build tools\" or \"Dev Tools\" builds"));
262 
263     auto additionalArguments = addAspect<StringAspect>();
264     additionalArguments->setSettingsKey(Constants::BUILDCONSOLE_ADDITIONALARGUMENTS);
265     additionalArguments->setLabelText(tr("Additional Arguments:"));
266     additionalArguments->setDisplayStyle(StringAspect::LineEditDisplay);
267     additionalArguments->setToolTip(tr("Add additional buildconsole arguments manually. "
268                                        "The value of this field will be concatenated to the "
269                                        "final buildconsole command line"));
270 
271     auto openMonitor = addAspect<BoolAspect>();
272     openMonitor->setSettingsKey(Constants::BUILDCONSOLE_OPENMONITOR);
273     openMonitor->setLabel(tr("Open Build Monitor:"));
274     openMonitor->setToolTip(tr("Opens Build Monitor once the build starts."));
275 
276     setCommandLineProvider([=] {
277         QStringList args;
278 
279         QString cmd("/Command= %1");
280         cmd = cmd.arg(commandBuilder->fullCommandFlag(keepJobNum->value()));
281         args.append(cmd);
282 
283         if (!profileXml->value().isEmpty())
284             args.append("/Profile=" + profileXml->value());
285 
286         args.append(QString("/AvoidLocal=%1").arg(avoidLocal->value() ? QString("ON") : QString("OFF")));
287 
288         if (maxCpu->value() > 0)
289             args.append(QString("/MaxCPUs=%1").arg(maxCpu->value()));
290 
291         if (!maxWinVer->stringValue().isEmpty())
292             args.append(QString("/MaxWinVer=%1").arg(normalizeWinVerArgument(maxWinVer->stringValue())));
293 
294         if (!minWinVer->stringValue().isEmpty())
295             args.append(QString("/MinWinVer=%1").arg(normalizeWinVerArgument(minWinVer->stringValue())));
296 
297         if (!title->value().isEmpty())
298             args.append(QString("/Title=" + title->value()));
299 
300         if (!monFile->value().isEmpty())
301             args.append(QString("/Mon=" + monFile->value()));
302 
303         if (suppressStdOut->value())
304             args.append("/Silent");
305 
306         if (!logFile->value().isEmpty())
307             args.append(QString("/Log=" + logFile->value()));
308 
309         if (showCmd->value())
310             args.append("/ShowCmd");
311 
312         if (showAgents->value())
313             args.append("/ShowAgent");
314 
315         if (showAgents->value())
316             args.append("/ShowTime");
317 
318         if (hideHeader->value())
319             args.append("/NoLogo");
320 
321         if (!logLevel->stringValue().isEmpty())
322             args.append(QString("/LogLevel=" + logLevel->stringValue()));
323 
324         if (!setEnv->value().isEmpty())
325             args.append(QString("/SetEnv=" + setEnv->value()));
326 
327         if (stopOnError->value())
328             args.append("/StopOnErrors");
329 
330         if (!additionalArguments->value().isEmpty())
331             args.append(additionalArguments->value());
332 
333         if (openMonitor->value())
334             args.append("/OpenMonitor");
335 
336         return CommandLine("BuildConsole.exe", args);
337     });
338 }
339 
setupOutputFormatter(OutputFormatter * formatter)340 void BuildConsoleBuildStep::setupOutputFormatter(OutputFormatter *formatter)
341 {
342     formatter->addLineParser(new GnuMakeParser());
343     formatter->addLineParsers(kit()->createOutputParsers());
344     formatter->addSearchDir(processParameters()->effectiveWorkingDirectory());
345     AbstractProcessStep::setupOutputFormatter(formatter);
346 }
347 
348 // BuildConsoleStepFactory
349 
BuildConsoleStepFactory()350 BuildConsoleStepFactory::BuildConsoleStepFactory()
351 {
352     registerStep<BuildConsoleBuildStep>(IncrediBuild::Constants::BUILDCONSOLE_BUILDSTEP_ID);
353     setDisplayName(BuildConsoleBuildStep::tr("IncrediBuild for Windows"));
354     setSupportedStepLists({ProjectExplorer::Constants::BUILDSTEPS_BUILD,
355                            ProjectExplorer::Constants::BUILDSTEPS_CLEAN});
356 }
357 
358 } // namespace Internal
359 } // namespace IncrediBuild
360