| 1 | // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. |
| 2 | // |
| 3 | // SPDX-License-Identifier: GPL-3.0-or-later |
| 4 | |
| 5 | #include "codeporting.h" |
| 6 | #include "common/util/custompaths.h" |
| 7 | |
| 8 | #include <QFile> |
| 9 | #include <QDebug> |
| 10 | #include <QRegularExpression> |
| 11 | #include <QDir> |
| 12 | |
| 13 | static QStringList kSrcItemNames{QObject::tr("FilePath" ), QObject::tr("CodeRange" ), |
| 14 | QObject::tr("Key" ), QObject::tr("Suggestion" ), QObject::tr("FileType" )}; |
| 15 | static QStringList kLibItemNames{QObject::tr("FileName" ), QObject::tr("Installed" ), QObject::tr("Detail" )}; |
| 16 | CodePorting::CodePorting(QObject *parent) |
| 17 | : QObject(parent) |
| 18 | { |
| 19 | connect(&process, &QProcess::started, [this]() { |
| 20 | this->updateStatus(kRuning); |
| 21 | QString startMsg = tr("Start execute command: \"%1\" \"%2\" in workspace \"%3\".\n" ) |
| 22 | .arg(process.program().split("/" ).last(), process.arguments().join(" " ), process.workingDirectory()); |
| 23 | emit outputInformation(startMsg, OutputPane::OutputFormat::NormalMessage); |
| 24 | }); |
| 25 | |
| 26 | connect(&process, &QProcess::readyReadStandardOutput, [&]() { |
| 27 | process.setReadChannel(QProcess::StandardOutput); |
| 28 | while (process.canReadLine()) { |
| 29 | QString line = QString::fromUtf8(process.readLine()); |
| 30 | auto mode = parseFormat(line); |
| 31 | emit outputInformation(line, OutputPane::OutputFormat::StdOut, mode); |
| 32 | } |
| 33 | }); |
| 34 | |
| 35 | connect(&process, &QProcess::readyReadStandardError, [&]() { |
| 36 | process.setReadChannel(QProcess::StandardError); |
| 37 | while (process.canReadLine()) { |
| 38 | QString line = QString::fromUtf8(process.readLine()); |
| 39 | QRegularExpression reg("\\s\\[INFO\\]\\s" ); |
| 40 | bool isInfo = reg.match(line).hasMatch(); |
| 41 | OutputPane::OutputFormat format = isInfo ? OutputPane::StdOut : OutputPane::StdErr; |
| 42 | auto mode = parseFormat(line); |
| 43 | emit outputInformation(line, format, mode); |
| 44 | |
| 45 | // The output content include report path, so get it. |
| 46 | QString reportPath = parseReportPath(line); |
| 47 | if (!reportPath.isEmpty()) { |
| 48 | bool bSuccessful = parseReportFromFile(reportPath); |
| 49 | if (bSuccessful) { |
| 50 | emit outputInformation(tr("Parse report successful.\n" ), OutputPane::StdOut, mode); |
| 51 | } else { |
| 52 | emit outputInformation(tr("Parse report Failed.\n" ), OutputPane::StdErr, mode); |
| 53 | } |
| 54 | } |
| 55 | } |
| 56 | }); |
| 57 | |
| 58 | connect(&process, static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished), |
| 59 | [&](int exitcode, QProcess::ExitStatus exitStatus) { |
| 60 | QString retMsg; |
| 61 | OutputPane::OutputFormat format = OutputPane::NormalMessage; |
| 62 | if (0 == exitcode && exitStatus == QProcess::ExitStatus::NormalExit) { |
| 63 | retMsg = tr("The process \"%1\" exited normally.\n" ).arg(process.program()); |
| 64 | this->updateStatus(kSuccessful); |
| 65 | } else if (exitStatus == QProcess::NormalExit) { |
| 66 | retMsg = tr("The process \"%1\" exited with code %2.\n" ) |
| 67 | .arg(process.program(), QString::number(exitcode)); |
| 68 | this->updateStatus(kSuccessful); |
| 69 | } else { |
| 70 | retMsg = tr("The process \"%1\" crashed.\n" ) |
| 71 | .arg(process.program()); |
| 72 | format = OutputPane::ErrorMessage; |
| 73 | this->updateStatus(kFailed); |
| 74 | } |
| 75 | emit outputInformation(retMsg, format); |
| 76 | }); |
| 77 | } |
| 78 | |
| 79 | bool CodePorting::start(const QString &projectSrcPath, const QString &srcCPU, const QString &buildDir, const QString &destCPU) |
| 80 | { |
| 81 | if (status == kRuning) |
| 82 | return false; |
| 83 | |
| 84 | // check script and source dirctory is exists. |
| 85 | QString scriptPath = CustomPaths::global(CustomPaths::Scripts); |
| 86 | QString portingCli = scriptPath + "/porting-script/code_porting.py" ; |
| 87 | QDir dir; |
| 88 | if (!QFile::exists(portingCli) || !dir.exists(projectSrcPath)) |
| 89 | return false; |
| 90 | |
| 91 | projSrcPath = projectSrcPath; |
| 92 | |
| 93 | process.setProgram(getPython()); |
| 94 | QStringList args; |
| 95 | args.append(portingCli); |
| 96 | args.append("-S" ); |
| 97 | args.append(projectSrcPath); |
| 98 | args.append("-B" ); |
| 99 | args.append(buildDir); |
| 100 | args.append("--scpu" ); |
| 101 | args.append(srcCPU); |
| 102 | args.append("--dcpu" ); |
| 103 | args.append(destCPU); |
| 104 | process.setArguments(args); |
| 105 | process.start(); |
| 106 | process.waitForFinished(-1); |
| 107 | |
| 108 | return true; |
| 109 | } |
| 110 | |
| 111 | bool CodePorting::abort() |
| 112 | { |
| 113 | return true; |
| 114 | } |
| 115 | |
| 116 | CodePorting::PortingStatus CodePorting::getStatus() const |
| 117 | { |
| 118 | return status; |
| 119 | } |
| 120 | |
| 121 | bool CodePorting::isRunning() |
| 122 | { |
| 123 | return status == kRuning; |
| 124 | } |
| 125 | |
| 126 | const CodePorting::Report &CodePorting::getReport() const |
| 127 | { |
| 128 | return report; |
| 129 | } |
| 130 | |
| 131 | const QStringList &CodePorting::getSrcItemNames() const |
| 132 | { |
| 133 | return kSrcItemNames; |
| 134 | } |
| 135 | |
| 136 | const QStringList &CodePorting::getLibItemNames() const |
| 137 | { |
| 138 | return kLibItemNames; |
| 139 | } |
| 140 | |
| 141 | const QList<QStringList> CodePorting::getSourceReport() const |
| 142 | { |
| 143 | return report["cppfiles" ]; |
| 144 | } |
| 145 | |
| 146 | const QList<QStringList> CodePorting::getDependLibReport() const |
| 147 | { |
| 148 | return report["sofiles" ]; |
| 149 | } |
| 150 | |
| 151 | /** |
| 152 | * @brief findAll |
| 153 | * @param pattern |
| 154 | * @param str |
| 155 | * @param Greedy: find all items matched when Greedy is true. |
| 156 | * @return matched items. |
| 157 | */ |
| 158 | QList<QString> findAll(QString pattern, QString str, bool Greedy) |
| 159 | { |
| 160 | QRegExp rxlen(pattern); |
| 161 | rxlen.setMinimal(Greedy); |
| 162 | int position = 0; |
| 163 | QList<QString> strList; |
| 164 | while (position >= 0) { |
| 165 | position = rxlen.indexIn(str, position); |
| 166 | if (position < 0) |
| 167 | break; |
| 168 | strList << rxlen.cap(1); |
| 169 | position += rxlen.matchedLength(); |
| 170 | } |
| 171 | return strList; |
| 172 | } |
| 173 | |
| 174 | /** |
| 175 | * @brief CodePorting::getPython |
| 176 | * get the latest version |
| 177 | * @return |
| 178 | */ |
| 179 | QString CodePorting::getPython() |
| 180 | { |
| 181 | if (pythonCmd.isEmpty()) { |
| 182 | QDir dir("/usr/bin" ); |
| 183 | QStringList filter { "Python*.*" }; |
| 184 | dir.setNameFilters(filter); |
| 185 | QStringList pythonList = dir.entryList(); |
| 186 | |
| 187 | QString pattern = "((\\d)|(\\d.\\d))($|\\s)" ; |
| 188 | QStringList versions = findAll(pattern, pythonList.join(" " ), true); |
| 189 | |
| 190 | double newVersion = 0; |
| 191 | for (auto version : versions) { |
| 192 | double v = version.toDouble(); |
| 193 | if (v > newVersion) { |
| 194 | newVersion = v; |
| 195 | } |
| 196 | } |
| 197 | pythonCmd = "python" + QString::number(newVersion); |
| 198 | } |
| 199 | return pythonCmd; |
| 200 | } |
| 201 | |
| 202 | void CodePorting::updateStatus(CodePorting::PortingStatus _status) |
| 203 | { |
| 204 | status = _status; |
| 205 | emit notifyPortingStatus(status); |
| 206 | } |
| 207 | |
| 208 | bool CodePorting::parseReportFromFile(const QString &reportPath) |
| 209 | { |
| 210 | bool successful = false; |
| 211 | |
| 212 | bool isReportExists = QFile::exists(reportPath); |
| 213 | qInfo() << "Report exists: " << isReportExists; |
| 214 | |
| 215 | if (isReportExists) { |
| 216 | QFile file(reportPath); |
| 217 | if (file.open(QIODevice::ReadOnly)) { |
| 218 | report.clear(); |
| 219 | const char *quotes = "\"" ; |
| 220 | while (!file.atEnd()) { |
| 221 | QString line = file.readLine(); |
| 222 | QStringList cols = line.split("\",\"" ); |
| 223 | if (cols.length() == kItemsCount) { |
| 224 | // remove redundant quotes |
| 225 | cols.first().remove(quotes); |
| 226 | cols.last().remove(quotes); |
| 227 | |
| 228 | QString type = cols[kFileType].simplified(); |
| 229 | if (report.find(type) == report.end()) { |
| 230 | report.insert(type, {cols}); |
| 231 | } else { |
| 232 | report[type].push_back(cols); |
| 233 | } |
| 234 | } |
| 235 | } |
| 236 | file.close(); |
| 237 | successful = true; |
| 238 | } |
| 239 | } |
| 240 | return successful; |
| 241 | } |
| 242 | |
| 243 | QString CodePorting::parseReportPath(const QString &line) |
| 244 | { |
| 245 | QString reportPath; |
| 246 | QRegularExpression reg("porting advisor for" ); |
| 247 | auto match = reg.match(line); |
| 248 | if (match.hasMatch()) { |
| 249 | reg.setPattern("(?<=\\s:\\s)(.*)" ); |
| 250 | match = reg.match(line); |
| 251 | if (match.hasMatch()) { |
| 252 | reportPath = match.captured(); |
| 253 | } |
| 254 | } |
| 255 | return reportPath; |
| 256 | } |
| 257 | |
| 258 | OutputPane::AppendMode CodePorting::parseFormat(const QString &line) |
| 259 | { |
| 260 | OutputPane::AppendMode mode = OutputPane::Normal; |
| 261 | QRegularExpression reg("Running task:" ); |
| 262 | auto match = reg.match(line); |
| 263 | if (match.hasMatch()) { |
| 264 | mode = OutputPane::OverWrite; |
| 265 | } |
| 266 | return mode; |
| 267 | } |
| 268 | |