1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2016 The Qt Company Ltd. |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the test suite of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and The Qt Company. For licensing terms |
14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at https://www.qt.io/contact-us. |
16 | ** |
17 | ** GNU General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT |
21 | ** included in the packaging of this file. Please review the following |
22 | ** information to ensure the GNU General Public License requirements will |
23 | ** be met: https://www.gnu.org/licenses/gpl-3.0.html. |
24 | ** |
25 | ** $QT_END_LICENSE$ |
26 | ** |
27 | ****************************************************************************/ |
28 | |
29 | #include "testcompiler.h" |
30 | |
31 | #include <QProcess> |
32 | #include <QDir> |
33 | |
34 | QString TestCompiler::targetName(BuildType buildMode, const QString& target, const QString& version) |
35 | { |
36 | Q_UNUSED(version); |
37 | QString targetName = target; |
38 | |
39 | #if defined (Q_OS_WIN32) |
40 | switch (buildMode) |
41 | { |
42 | case Exe: // app |
43 | targetName.append(".exe" ); |
44 | break; |
45 | case Dll: // dll |
46 | if (!version.isEmpty()) |
47 | targetName.append(version.section("." , 0, 0)); |
48 | targetName.append(".dll" ); |
49 | break; |
50 | case Lib: // lib |
51 | #ifdef Q_CC_GNU |
52 | targetName.prepend("lib" ); |
53 | targetName.append(".a" ); |
54 | #else |
55 | targetName.append(".lib" ); |
56 | #endif |
57 | break; |
58 | case Plain: |
59 | // no conversion |
60 | break; |
61 | } |
62 | #elif defined( Q_OS_MAC ) |
63 | switch (buildMode) |
64 | { |
65 | case Exe: // app |
66 | targetName += ".app/Contents/MacOS/" + target.section('/', -1); |
67 | break; |
68 | case Dll: // dll |
69 | targetName.prepend("lib" ); |
70 | if (!version.isEmpty()) |
71 | targetName.append('.' + version); |
72 | targetName.append(".dylib" ); |
73 | break; |
74 | case Lib: // lib |
75 | targetName.prepend("lib" ); |
76 | targetName.append(".a" ); |
77 | break; |
78 | case Plain: |
79 | // no conversion |
80 | break; |
81 | } |
82 | #else |
83 | switch (buildMode) |
84 | { |
85 | case Exe: // app |
86 | break; |
87 | case Dll: // dll |
88 | targetName.prepend(s: "lib" ); |
89 | #if defined (Q_OS_HPUX) && !defined (__ia64) |
90 | targetName.append(".sl" ); |
91 | #elif defined (Q_OS_AIX) |
92 | targetName.append(".a" ); |
93 | #else |
94 | targetName.append(s: ".so" ); |
95 | #endif |
96 | break; |
97 | case Lib: // lib |
98 | targetName.prepend(s: "lib" ); |
99 | targetName.append(s: ".a" ); |
100 | break; |
101 | case Plain: |
102 | // no conversion |
103 | break; |
104 | } |
105 | #endif |
106 | return targetName; |
107 | } |
108 | |
109 | TestCompiler::TestCompiler() |
110 | { |
111 | setBaseCommands( makeCmd: "" , qmakeCmd: "" ); |
112 | } |
113 | |
114 | TestCompiler::~TestCompiler() |
115 | { |
116 | } |
117 | |
118 | bool TestCompiler::errorOut() |
119 | { |
120 | qDebug(msg: "%s" , qPrintable(testOutput_.join(QStringLiteral("\n" )))); |
121 | return false; |
122 | } |
123 | |
124 | // Return the system environment, remove MAKEFLAGS variable in |
125 | // case the CI uses jom passing flags incompatible to nmake |
126 | // or vice versa. |
127 | static inline QStringList systemEnvironment() |
128 | { |
129 | #ifdef Q_OS_WIN |
130 | static QStringList result; |
131 | if (result.isEmpty()) { |
132 | foreach (const QString &variable, QProcess::systemEnvironment()) { |
133 | if (variable.startsWith(QStringLiteral("MAKEFLAGS=" ), Qt::CaseInsensitive)) { |
134 | qWarning("Removing environment setting '%s'" , qPrintable(variable)); |
135 | } else { |
136 | result.push_back(variable); |
137 | } |
138 | } |
139 | } |
140 | #else |
141 | static const QStringList result = QProcess::systemEnvironment(); |
142 | #endif // ifdef Q_OS_WIN |
143 | return result; |
144 | } |
145 | |
146 | bool TestCompiler::runCommand(const QString &cmd, const QStringList &args, bool expectFail) |
147 | { |
148 | QString dbg = cmd; |
149 | if (dbg.contains(c: ' ')) |
150 | dbg.prepend(c: '"').append(c: '"'); |
151 | foreach (QString arg, args) { |
152 | if (arg.contains(c: ' ')) |
153 | arg.prepend(c: '"').append(c: '"'); |
154 | dbg.append(c: ' ').append(s: arg); |
155 | } |
156 | testOutput_.append(t: "Running command: " + dbg); |
157 | |
158 | QProcess child; |
159 | child.setEnvironment(systemEnvironment() + environment_); |
160 | |
161 | child.start(program: cmd, arguments: args); |
162 | if (!child.waitForStarted(msecs: -1)) { |
163 | testOutput_.append( t: "Unable to start child process." ); |
164 | return errorOut(); |
165 | } |
166 | |
167 | child.waitForFinished(msecs: -1); |
168 | bool ok = child.exitStatus() == QProcess::NormalExit && (expectFail ^ (child.exitCode() == 0)); |
169 | |
170 | for (auto channel : { QProcess::StandardOutput, QProcess::StandardError }) { |
171 | child.setReadChannel(channel); |
172 | while (!child.atEnd()) { |
173 | const QString output = QString::fromLocal8Bit(str: child.readLine()); |
174 | if (output.startsWith(s: "Project MESSAGE: FAILED" )) |
175 | ok = false; |
176 | testOutput_.append(t: output); |
177 | } |
178 | } |
179 | |
180 | return ok ? true : errorOut(); |
181 | } |
182 | |
183 | void TestCompiler::setBaseCommands( QString makeCmd, QString qmakeCmd ) |
184 | { |
185 | makeCmd_ = makeCmd; |
186 | qmakeCmd_ = qmakeCmd; |
187 | } |
188 | |
189 | void TestCompiler::resetArguments() |
190 | { |
191 | makeArgs_.clear(); |
192 | qmakeArgs_.clear(); |
193 | } |
194 | |
195 | void TestCompiler::setArguments(const QStringList &makeArgs, const QStringList &qmakeArgs) |
196 | { |
197 | makeArgs_ = makeArgs; |
198 | qmakeArgs_ = qmakeArgs; |
199 | } |
200 | |
201 | void TestCompiler::resetEnvironment() |
202 | { |
203 | environment_.clear(); |
204 | } |
205 | |
206 | void TestCompiler::addToEnvironment( QString varAssignment ) |
207 | { |
208 | environment_.push_back(t: varAssignment); |
209 | } |
210 | |
211 | bool TestCompiler::makeClean( const QString &workPath ) |
212 | { |
213 | QDir D; |
214 | if (!D.exists(name: workPath)) { |
215 | testOutput_.append( t: "Directory '" + workPath + "' doesn't exist" ); |
216 | return errorOut(); |
217 | } |
218 | |
219 | D.setCurrent(workPath); |
220 | QFileInfo Fi( workPath + "/Makefile" ); |
221 | if (Fi.exists()) |
222 | // Run make clean |
223 | return runCommand(cmd: makeCmd_, args: QStringList() << "clean" ); |
224 | |
225 | return true; |
226 | } |
227 | |
228 | bool TestCompiler::makeDistClean( const QString &workPath ) |
229 | { |
230 | QDir D; |
231 | if (!D.exists(name: workPath)) { |
232 | testOutput_.append( t: "Directory '" + workPath + "' doesn't exist" ); |
233 | return errorOut(); |
234 | } |
235 | |
236 | D.setCurrent(workPath); |
237 | QFileInfo Fi( workPath + "/Makefile" ); |
238 | if (Fi.exists()) |
239 | // Run make distclean |
240 | return runCommand(cmd: makeCmd_, args: QStringList() << "distclean" ); |
241 | |
242 | return true; |
243 | |
244 | } |
245 | |
246 | bool TestCompiler::qmakeProject( const QString &workDir, const QString &proName ) |
247 | { |
248 | QDir D; |
249 | if (!D.exists(name: workDir)) { |
250 | testOutput_.append( t: "Directory '" + workDir + "' doesn't exist" ); |
251 | return errorOut(); |
252 | } |
253 | D.setCurrent(workDir); |
254 | |
255 | QString projectFile = proName; |
256 | if (!projectFile.endsWith(s: ".pro" )) |
257 | projectFile += ".pro" ; |
258 | |
259 | return runCommand(cmd: qmakeCmd_, args: QStringList() << "-project" << "-o" << projectFile << "DESTDIR=./" ); |
260 | } |
261 | |
262 | bool TestCompiler::qmake(const QString &workDir, const QString &proName, const QString &buildDir, |
263 | const QStringList &additionalArguments) |
264 | { |
265 | QDir D; |
266 | D.setCurrent( workDir ); |
267 | |
268 | if (D.exists(name: "Makefile" )) |
269 | D.remove(fileName: "Makefile" ); |
270 | |
271 | QString projectFile = proName; |
272 | QString makeFile = buildDir; |
273 | if (!projectFile.endsWith(s: ".pro" )) |
274 | projectFile += ".pro" ; |
275 | if (!makeFile.isEmpty() && !makeFile.endsWith(c: '/')) |
276 | makeFile += '/'; |
277 | makeFile += "Makefile" ; |
278 | |
279 | // Now start qmake and generate the makefile |
280 | return runCommand(cmd: qmakeCmd_, args: QStringList(qmakeArgs_) << projectFile << "-o" << makeFile |
281 | << additionalArguments); |
282 | } |
283 | |
284 | bool TestCompiler::qmake(const QString &workDir, const QStringList &arguments) |
285 | { |
286 | QDir d; |
287 | d.setCurrent(workDir); // ### runCommand should take a workingDir argument instead |
288 | return runCommand(cmd: qmakeCmd_, args: arguments); |
289 | } |
290 | |
291 | bool TestCompiler::make( const QString &workPath, const QString &target, bool expectFail ) |
292 | { |
293 | QDir D; |
294 | D.setCurrent( workPath ); |
295 | |
296 | QStringList args = makeArgs_; |
297 | if (makeCmd_.contains(s: "nmake" , cs: Qt::CaseInsensitive) || |
298 | makeCmd_.contains(s: "jom" , cs: Qt::CaseInsensitive)) { |
299 | args << "/NOLOGO" ; |
300 | } |
301 | if (!target.isEmpty()) |
302 | args << target; |
303 | |
304 | return runCommand(cmd: makeCmd_, args, expectFail); |
305 | } |
306 | |
307 | bool TestCompiler::exists( const QString &destDir, const QString &exeName, BuildType buildType, const QString &version ) |
308 | { |
309 | QFileInfo f(destDir + QLatin1Char('/') + targetName(buildMode: buildType, target: exeName, version)); |
310 | return f.exists(); |
311 | } |
312 | |
313 | bool TestCompiler::removeMakefile( const QString &workPath ) |
314 | { |
315 | return removeFile( workPath, fileName: "Makefile" ); |
316 | } |
317 | |
318 | bool TestCompiler::removeProject( const QString &workPath, const QString &project ) |
319 | { |
320 | QString projectFile = project; |
321 | if (!projectFile.endsWith(s: ".pro" )) |
322 | projectFile += ".pro" ; |
323 | |
324 | return removeFile( workPath, fileName: projectFile ); |
325 | } |
326 | |
327 | bool TestCompiler::removeFile( const QString &workPath, const QString &fileName ) |
328 | { |
329 | QDir D; |
330 | D.setCurrent( workPath ); |
331 | |
332 | return ( D.exists( name: fileName ) ) ? D.remove( fileName ) : true; |
333 | } |
334 | |
335 | QString TestCompiler::commandOutput() const |
336 | { |
337 | #ifndef Q_OS_WIN |
338 | return testOutput_.join(sep: "\n" ); |
339 | #else |
340 | return testOutput_.join("\r\n" ); |
341 | #endif |
342 | } |
343 | |
344 | void TestCompiler::clearCommandOutput() |
345 | { |
346 | testOutput_.clear(); |
347 | } |
348 | |