| 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 | |