1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 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 "qmakemakestep.h"
27
28 #include "qmakeparser.h"
29 #include "qmakeproject.h"
30 #include "qmakenodes.h"
31 #include "qmakebuildconfiguration.h"
32 #include "qmakeprojectmanagerconstants.h"
33 #include "qmakesettings.h"
34 #include "qmakestep.h"
35
36 #include <projectexplorer/target.h>
37 #include <projectexplorer/toolchain.h>
38 #include <projectexplorer/buildsteplist.h>
39 #include <projectexplorer/gnumakeparser.h>
40 #include <projectexplorer/processparameters.h>
41 #include <projectexplorer/projectexplorer.h>
42 #include <projectexplorer/projectexplorerconstants.h>
43 #include <projectexplorer/kitinformation.h>
44 #include <projectexplorer/xcodebuildparser.h>
45
46 #include <utils/qtcprocess.h>
47 #include <utils/variablechooser.h>
48
49 #include <QDir>
50 #include <QFileInfo>
51
52 using namespace ProjectExplorer;
53 using namespace Utils;
54
55 namespace QmakeProjectManager {
56 namespace Internal {
57
58 class QmakeMakeStep : public MakeStep
59 {
60 Q_DECLARE_TR_FUNCTIONS(QmakeProjectManager::QmakeMakeStep)
61
62 public:
63 QmakeMakeStep(BuildStepList *bsl, Id id);
64
65 private:
66 void finish(bool success) override;
67 bool init() override;
68 void setupOutputFormatter(OutputFormatter *formatter) override;
69 void doRun() override;
70 QStringList displayArguments() const override;
71
72 bool m_scriptTarget = false;
73 FilePath m_makeFileToCheck;
74 bool m_unalignedBuildDir;
75 bool m_ignoredNonTopLevelBuild = false;
76 };
77
QmakeMakeStep(BuildStepList * bsl,Id id)78 QmakeMakeStep::QmakeMakeStep(BuildStepList *bsl, Id id)
79 : MakeStep(bsl, id)
80 {
81 if (bsl->id() == ProjectExplorer::Constants::BUILDSTEPS_CLEAN) {
82 setIgnoreReturnValue(true);
83 setUserArguments("clean");
84 }
85 supportDisablingForSubdirs();
86 }
87
init()88 bool QmakeMakeStep::init()
89 {
90 // Note: This skips the Makestep::init() level.
91 if (!AbstractProcessStep::init())
92 return false;
93
94 const auto bc = static_cast<QmakeBuildConfiguration *>(buildConfiguration());
95
96 const CommandLine unmodifiedMake = effectiveMakeCommand(Execution);
97 const FilePath makeExecutable = unmodifiedMake.executable();
98 if (makeExecutable.isEmpty())
99 emit addTask(makeCommandMissingTask());
100
101 if (!bc || makeExecutable.isEmpty()) {
102 emitFaultyConfigurationMessage();
103 return false;
104 }
105
106 // Ignore all but the first make step for a non-top-level build. See QTCREATORBUG-15794.
107 m_ignoredNonTopLevelBuild = (bc->fileNodeBuild() || bc->subNodeBuild()) && !enabledForSubDirs();
108
109 ProcessParameters *pp = processParameters();
110 pp->setMacroExpander(bc->macroExpander());
111
112 FilePath workingDirectory;
113 if (bc->subNodeBuild())
114 workingDirectory = bc->qmakeBuildSystem()->buildDir(bc->subNodeBuild()->filePath());
115 else
116 workingDirectory = bc->buildDirectory();
117 pp->setWorkingDirectory(workingDirectory);
118
119 CommandLine makeCmd(makeExecutable);
120
121 QmakeProjectManager::QmakeProFileNode *subProFile = bc->subNodeBuild();
122 if (subProFile) {
123 QString makefile = subProFile->makefile();
124 if (makefile.isEmpty())
125 makefile = "Makefile";
126 // Use Makefile.Debug and Makefile.Release
127 // for file builds, since the rules for that are
128 // only in those files.
129 if (subProFile->isDebugAndRelease() && bc->fileNodeBuild()) {
130 if (buildType() == QmakeBuildConfiguration::Debug)
131 makefile += ".Debug";
132 else
133 makefile += ".Release";
134 }
135
136 if (makefile != "Makefile")
137 makeCmd.addArgs({"-f", makefile});
138
139 m_makeFileToCheck = workingDirectory / makefile;
140 } else {
141 QString makefile = bc->makefile();
142 if (!makefile.isEmpty()) {
143 makeCmd.addArgs({"-f", makefile});
144 m_makeFileToCheck = workingDirectory / makefile;
145 } else {
146 m_makeFileToCheck = workingDirectory / "Makefile";
147 }
148 }
149
150 makeCmd.addArgs(unmodifiedMake.arguments(), CommandLine::Raw);
151
152 if (bc->fileNodeBuild() && subProFile) {
153 QString objectsDir = subProFile->objectsDirectory();
154 if (objectsDir.isEmpty()) {
155 objectsDir = bc->qmakeBuildSystem()->buildDir(subProFile->filePath()).toString();
156 if (subProFile->isDebugAndRelease()) {
157 if (bc->buildType() == QmakeBuildConfiguration::Debug)
158 objectsDir += "/debug";
159 else
160 objectsDir += "/release";
161 }
162 }
163
164 if (subProFile->isObjectParallelToSource()) {
165 const FilePath sourceFileDir = bc->fileNodeBuild()->filePath().parentDir();
166 const FilePath proFileDir = subProFile->proFile()->sourceDir().canonicalPath();
167 if (!objectsDir.endsWith('/'))
168 objectsDir += QLatin1Char('/');
169 objectsDir += sourceFileDir.relativeChildPath(proFileDir).toString();
170 objectsDir = QDir::cleanPath(objectsDir);
171 }
172
173 QString relObjectsDir = QDir(pp->workingDirectory().toString())
174 .relativeFilePath(objectsDir);
175 if (relObjectsDir == ".")
176 relObjectsDir.clear();
177 if (!relObjectsDir.isEmpty())
178 relObjectsDir += '/';
179 QString objectFile = relObjectsDir + bc->fileNodeBuild()->filePath().baseName()
180 + subProFile->objectExtension();
181 makeCmd.addArg(objectFile);
182 }
183
184 pp->setEnvironment(makeEnvironment());
185 pp->setCommandLine(makeCmd);
186
187 auto rootNode = dynamic_cast<QmakeProFileNode *>(project()->rootProjectNode());
188 QTC_ASSERT(rootNode, return false);
189 m_scriptTarget = rootNode->projectType() == ProjectType::ScriptTemplate;
190 m_unalignedBuildDir = !bc->isBuildDirAtSafeLocation();
191
192 // A user doing "make clean" indicates they want a proper rebuild, so make sure to really
193 // execute qmake on the next build.
194 if (stepList()->id() == ProjectExplorer::Constants::BUILDSTEPS_CLEAN) {
195 const auto qmakeStep = bc->qmakeStep();
196 if (qmakeStep)
197 qmakeStep->setForced(true);
198 }
199
200 return true;
201 }
202
setupOutputFormatter(OutputFormatter * formatter)203 void QmakeMakeStep::setupOutputFormatter(OutputFormatter *formatter)
204 {
205 formatter->addLineParser(new GnuMakeParser());
206 ToolChain *tc = ToolChainKitAspect::cxxToolChain(kit());
207 OutputTaskParser *xcodeBuildParser = nullptr;
208 if (tc && tc->targetAbi().os() == Abi::DarwinOS) {
209 xcodeBuildParser = new XcodebuildParser;
210 formatter->addLineParser(xcodeBuildParser);
211 }
212 QList<OutputLineParser *> additionalParsers = kit()->createOutputParsers();
213
214 // make may cause qmake to be run, add last to make sure it has a low priority.
215 additionalParsers << new QMakeParser;
216
217 if (xcodeBuildParser) {
218 for (OutputLineParser * const p : qAsConst(additionalParsers))
219 p->setRedirectionDetector(xcodeBuildParser);
220 }
221 formatter->addLineParsers(additionalParsers);
222 formatter->addSearchDir(processParameters()->effectiveWorkingDirectory());
223
224 AbstractProcessStep::setupOutputFormatter(formatter);
225 }
226
doRun()227 void QmakeMakeStep::doRun()
228 {
229 if (m_scriptTarget || m_ignoredNonTopLevelBuild) {
230 emit finished(true);
231 return;
232 }
233
234 if (!m_makeFileToCheck.exists()) {
235 if (!ignoreReturnValue())
236 emit addOutput(tr("Cannot find Makefile. Check your build settings."), BuildStep::OutputFormat::NormalMessage);
237 const bool success = ignoreReturnValue();
238 emit finished(success);
239 return;
240 }
241
242 AbstractProcessStep::doRun();
243 }
244
finish(bool success)245 void QmakeMakeStep::finish(bool success)
246 {
247 if (!success && !isCanceled() && m_unalignedBuildDir
248 && QmakeSettings::warnAgainstUnalignedBuildDir()) {
249 const QString msg = tr("The build directory is not at the same level as the source "
250 "directory, which could be the reason for the build failure.");
251 emit addTask(BuildSystemTask(Task::Warning, msg));
252 }
253 MakeStep::finish(success);
254 }
255
displayArguments() const256 QStringList QmakeMakeStep::displayArguments() const
257 {
258 const auto bc = static_cast<QmakeBuildConfiguration *>(buildConfiguration());
259 if (bc && !bc->makefile().isEmpty())
260 return {"-f", bc->makefile()};
261 return {};
262 }
263
264 ///
265 // QmakeMakeStepFactory
266 ///
267
QmakeMakeStepFactory()268 QmakeMakeStepFactory::QmakeMakeStepFactory()
269 {
270 registerStep<QmakeMakeStep>(Constants::MAKESTEP_BS_ID);
271 setSupportedProjectType(Constants::QMAKEPROJECT_ID);
272 setDisplayName(MakeStep::defaultDisplayName());
273 }
274
275 } // Internal
276 } // QmakeProjectManager
277