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