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
13static QStringList kSrcItemNames{QObject::tr("FilePath"), QObject::tr("CodeRange"),
14 QObject::tr("Key"), QObject::tr("Suggestion"), QObject::tr("FileType")};
15static QStringList kLibItemNames{QObject::tr("FileName"), QObject::tr("Installed"), QObject::tr("Detail")};
16CodePorting::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
79bool 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
111bool CodePorting::abort()
112{
113 return true;
114}
115
116CodePorting::PortingStatus CodePorting::getStatus() const
117{
118 return status;
119}
120
121bool CodePorting::isRunning()
122{
123 return status == kRuning;
124}
125
126const CodePorting::Report &CodePorting::getReport() const
127{
128 return report;
129}
130
131const QStringList &CodePorting::getSrcItemNames() const
132{
133 return kSrcItemNames;
134}
135
136const QStringList &CodePorting::getLibItemNames() const
137{
138 return kLibItemNames;
139}
140
141const QList<QStringList> CodePorting::getSourceReport() const
142{
143 return report["cppfiles"];
144}
145
146const 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 */
158QList<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 */
179QString 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
202void CodePorting::updateStatus(CodePorting::PortingStatus _status)
203{
204 status = _status;
205 emit notifyPortingStatus(status);
206}
207
208bool 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
243QString 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
258OutputPane::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