Move from command line to argv array (#216)
* command line -> argv array * update function names * update directory structure --------- Co-authored-by: Roy Qu <royqh1979@gmail.com>
This commit is contained in:
parent
85efc86cde
commit
18318653d7
|
@ -0,0 +1,30 @@
|
|||
name: Unit
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
ubuntu_makefile_escape:
|
||||
name: Unix makefile escape
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Setup xmake
|
||||
uses: xmake-io/github-action-setup-xmake@v1
|
||||
with:
|
||||
xmake-version: '2.8.6'
|
||||
|
||||
- name: Setup Qt
|
||||
uses: ConorMacBride/install-package@v1
|
||||
with:
|
||||
apt: libqt5svg5-dev qtbase5-dev qtbase5-dev-tools qttools5-dev-tools
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
xmake f --qt=/usr
|
||||
xmake b test-escape
|
||||
|
||||
- name: Test
|
||||
run: |
|
||||
export QT_ASSUME_STDERR_HAS_CONSOLE=1
|
||||
xmake r test-escape
|
3
NEWS.md
3
NEWS.md
|
@ -13,6 +13,9 @@ Red Panda C++ Version 2.27
|
|||
- enhancement: Display ascii control chars.
|
||||
- fix: Parser: invalidating file may lost class inheritance infos.
|
||||
- fix: Function argument infos are not correctly parsed.
|
||||
- enhancement: Migrate external calls from command string to argv array to improve safety and security.
|
||||
- enhancement: Support POSIX shell-like escaping in user inputs for compiler arguments.
|
||||
- fix: (Hopefully) properly escape filenames and arguments in makefile generation.
|
||||
|
||||
Red Panda C++ Version 2.26
|
||||
- enhancement: Code suggestion for embedded std::vectors.
|
||||
|
|
|
@ -197,6 +197,8 @@ SOURCES += \
|
|||
settingsdialog/settingswidget.cpp \
|
||||
systemconsts.cpp \
|
||||
utils.cpp \
|
||||
utils/escape.cpp \
|
||||
utils/parsearg.cpp \
|
||||
widgets/coloredit.cpp \
|
||||
widgets/compileargumentswidget.cpp \
|
||||
widgets/consolewidget.cpp \
|
||||
|
@ -324,6 +326,8 @@ HEADERS += \
|
|||
settingsdialog/settingswidget.h \
|
||||
systemconsts.h \
|
||||
utils.h \
|
||||
utils/escape.h \
|
||||
utils/parsearg.h \
|
||||
common.h \
|
||||
widgets/coloredit.h \
|
||||
widgets/compileargumentswidget.h \
|
||||
|
|
|
@ -16,6 +16,8 @@
|
|||
*/
|
||||
#include "compiler.h"
|
||||
#include "utils.h"
|
||||
#include "utils/escape.h"
|
||||
#include "utils/parsearg.h"
|
||||
#include "compilermanager.h"
|
||||
#include "../systemconsts.h"
|
||||
|
||||
|
@ -68,10 +70,11 @@ void Compiler::run()
|
|||
for(int i=0;i<mExtraArgumentsList.count();i++) {
|
||||
if (!beforeRunExtraCommand(i))
|
||||
break;
|
||||
QString command = escapeCommandForLog(mExtraCompilersList[i], mExtraArgumentsList[i]);
|
||||
if (mExtraOutputFilesList[i].isEmpty()) {
|
||||
log(tr(" - Command: %1 %2").arg(extractFileName(mExtraCompilersList[i]),mExtraArgumentsList[i]));
|
||||
log(tr(" - Command: %1").arg(command));
|
||||
} else {
|
||||
log(tr(" - Command: %1 %2 > \"%3\"").arg(extractFileName(mExtraCompilersList[i]), mExtraArgumentsList[i], mExtraOutputFilesList[i]));
|
||||
log(tr(" - Command: %1 > %2").arg(command, escapeArgumentForPlatformShell(mExtraOutputFilesList[i], false)));
|
||||
}
|
||||
runCommand(mExtraCompilersList[i],mExtraArgumentsList[i],mDirectory, pipedText(),mExtraOutputFilesList[i]);
|
||||
}
|
||||
|
@ -331,9 +334,9 @@ void Compiler::stopCompile()
|
|||
mStop = true;
|
||||
}
|
||||
|
||||
QString Compiler::getCharsetArgument(const QByteArray& encoding,FileType fileType, bool checkSyntax)
|
||||
QStringList Compiler::getCharsetArgument(const QByteArray& encoding,FileType fileType, bool checkSyntax)
|
||||
{
|
||||
QString result;
|
||||
QStringList result;
|
||||
bool forceExecUTF8=false;
|
||||
// test if force utf8 from autolink infos
|
||||
if ((fileType == FileType::CSource ||
|
||||
|
@ -383,21 +386,22 @@ QString Compiler::getCharsetArgument(const QByteArray& encoding,FileType fileTyp
|
|||
}
|
||||
//qDebug()<<encodingName<<execEncodingName;
|
||||
if (checkSyntax) {
|
||||
result += QString(" -finput-charset=%1")
|
||||
.arg(encodingName);
|
||||
result << "-finput-charset=" + encodingName;
|
||||
} else if (encodingName!=execEncodingName) {
|
||||
result += QString(" -finput-charset=%1 -fexec-charset=%2")
|
||||
.arg(encodingName, execEncodingName);
|
||||
result += {
|
||||
"-finput-charset=" + encodingName,
|
||||
"-fexec-charset=" + execEncodingName,
|
||||
};
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
QString Compiler::getCCompileArguments(bool checkSyntax)
|
||||
QStringList Compiler::getCCompileArguments(bool checkSyntax)
|
||||
{
|
||||
QString result;
|
||||
QStringList result;
|
||||
if (checkSyntax) {
|
||||
result += " -fsyntax-only";
|
||||
result << "-fsyntax-only";
|
||||
}
|
||||
|
||||
QMap<QString, QString> compileOptions;
|
||||
|
@ -412,38 +416,36 @@ QString Compiler::getCCompileArguments(bool checkSyntax)
|
|||
PCompilerOption pOption = CompilerInfoManager::getCompilerOption(compilerSet()->compilerType(), key);
|
||||
if (pOption && pOption->isC && !pOption->isLinker) {
|
||||
if (pOption->type == CompilerOptionType::Checkbox)
|
||||
result += " " + pOption->setting;
|
||||
result << pOption->setting;
|
||||
else if (pOption->type == CompilerOptionType::Input)
|
||||
result += " " + pOption->setting + " " + compileOptions[key];
|
||||
result += {pOption->setting, compileOptions[key]};
|
||||
else {
|
||||
result += " " + pOption->setting + compileOptions[key];
|
||||
result << pOption->setting + compileOptions[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QMap<QString, QString> macros = devCppMacroVariables();
|
||||
|
||||
if (compilerSet()->useCustomCompileParams() && !compilerSet()->customCompileParams().isEmpty()) {
|
||||
QStringList params = textToLines(compilerSet()->customCompileParams());
|
||||
foreach(const QString& param, params)
|
||||
result += " "+ parseMacros(param);
|
||||
result << parseArguments(compilerSet()->customCompileParams(), macros, true);
|
||||
}
|
||||
|
||||
if (mProject) {
|
||||
QString s = mProject->options().compilerCmd;
|
||||
if (!s.isEmpty()) {
|
||||
s.replace("_@@_", " ");
|
||||
QStringList params = textToLines(s);
|
||||
foreach(const QString& param, params)
|
||||
result += " "+ parseMacros(param);
|
||||
result << parseArguments(s, macros, true);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
QString Compiler::getCppCompileArguments(bool checkSyntax)
|
||||
QStringList Compiler::getCppCompileArguments(bool checkSyntax)
|
||||
{
|
||||
QString result;
|
||||
QStringList result;
|
||||
if (checkSyntax) {
|
||||
result += " -fsyntax-only";
|
||||
result << "-fsyntax-only";
|
||||
}
|
||||
QMap<QString, QString> compileOptions;
|
||||
if (mProject && !mProject->options().compilerOptions.isEmpty()) {
|
||||
|
@ -457,75 +459,73 @@ QString Compiler::getCppCompileArguments(bool checkSyntax)
|
|||
PCompilerOption pOption = CompilerInfoManager::getCompilerOption(compilerSet()->compilerType(), key);
|
||||
if (pOption && pOption->isCpp && !pOption->isLinker) {
|
||||
if (pOption->type == CompilerOptionType::Checkbox)
|
||||
result += " " + pOption->setting;
|
||||
result << pOption->setting;
|
||||
else if (pOption->type == CompilerOptionType::Input)
|
||||
result += " " + pOption->setting + " " + compileOptions[key];
|
||||
result += {pOption->setting, compileOptions[key]};
|
||||
else {
|
||||
result += " " + pOption->setting + compileOptions[key];
|
||||
result << pOption->setting + compileOptions[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QMap<QString, QString> macros = devCppMacroVariables();
|
||||
if (compilerSet()->useCustomCompileParams() && !compilerSet()->customCompileParams().isEmpty()) {
|
||||
QStringList params = textToLines(compilerSet()->customCompileParams());
|
||||
foreach(const QString& param, params)
|
||||
result += " "+ parseMacros(param);
|
||||
result << parseArguments(compilerSet()->customCompileParams(), macros, true);
|
||||
}
|
||||
if (mProject) {
|
||||
QString s = mProject->options().cppCompilerCmd;
|
||||
if (!s.isEmpty()) {
|
||||
s.replace("_@@_", " ");
|
||||
QStringList params = textToLines(s);
|
||||
foreach(const QString& param, params)
|
||||
result += " "+ parseMacros(param);
|
||||
result << parseArguments(s, macros, true);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
QString Compiler::getCIncludeArguments()
|
||||
QStringList Compiler::getCIncludeArguments()
|
||||
{
|
||||
QString result;
|
||||
QStringList result;
|
||||
foreach (const QString& folder,compilerSet()->CIncludeDirs()) {
|
||||
result += QString(" -I\"%1\"").arg(folder);
|
||||
result << "-I" + folder;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
QString Compiler::getProjectIncludeArguments()
|
||||
QStringList Compiler::getProjectIncludeArguments()
|
||||
{
|
||||
QString result;
|
||||
QStringList result;
|
||||
if (mProject) {
|
||||
foreach (const QString& folder,mProject->options().includeDirs) {
|
||||
result += QString(" -I\"%1\"").arg(folder);
|
||||
result << "-I" + folder;
|
||||
}
|
||||
// result += QString(" -I\"%1\"").arg(extractFilePath(mProject->filename()));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
QString Compiler::getCppIncludeArguments()
|
||||
QStringList Compiler::getCppIncludeArguments()
|
||||
{
|
||||
QString result;
|
||||
QStringList result;
|
||||
foreach (const QString& folder,compilerSet()->CppIncludeDirs()) {
|
||||
result += QString(" -I\"%1\"").arg(folder);
|
||||
result << "-I" + folder;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
QString Compiler::getLibraryArguments(FileType fileType)
|
||||
QStringList Compiler::getLibraryArguments(FileType fileType)
|
||||
{
|
||||
QString result;
|
||||
QStringList result;
|
||||
|
||||
//Add libraries
|
||||
foreach (const QString& folder, compilerSet()->libDirs()) {
|
||||
result += QString(" -L\"%1\"").arg(folder);
|
||||
result << "-L" + folder;
|
||||
}
|
||||
|
||||
//add libs added via project
|
||||
if (mProject) {
|
||||
foreach (const QString& folder, mProject->options().libDirs){
|
||||
result += QString(" -L\"%1\"").arg(folder);
|
||||
result << "-L" + folder;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -547,9 +547,7 @@ QString Compiler::getLibraryArguments(FileType fileType)
|
|||
}
|
||||
if (waitCount<=10) {
|
||||
QSet<QString> parsedFiles;
|
||||
result += parseFileIncludesForAutolink(
|
||||
mFilename,
|
||||
parsedFiles);
|
||||
result += parseFileIncludesForAutolink(mFilename, parsedFiles);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -567,11 +565,11 @@ QString Compiler::getLibraryArguments(FileType fileType)
|
|||
PCompilerOption pOption = CompilerInfoManager::getCompilerOption(compilerSet()->compilerType(), key);
|
||||
if (pOption && pOption->isLinker) {
|
||||
if (pOption->type == CompilerOptionType::Checkbox)
|
||||
result += " " + pOption->setting;
|
||||
result << pOption->setting;
|
||||
else if (pOption->type == CompilerOptionType::Input)
|
||||
result += " " + pOption->setting + " " + compileOptions[key];
|
||||
result += {pOption->setting, compileOptions[key]};
|
||||
else {
|
||||
result += " " + pOption->setting + compileOptions[key];
|
||||
result << pOption->setting + compileOptions[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -581,47 +579,43 @@ QString Compiler::getLibraryArguments(FileType fileType)
|
|||
QStringList params = textToLines(compilerSet()->customLinkParams());
|
||||
if (!params.isEmpty()) {
|
||||
foreach(const QString& param, params)
|
||||
result += " " + param;
|
||||
result << param;
|
||||
}
|
||||
}
|
||||
|
||||
if (mProject) {
|
||||
if (mProject->options().type == ProjectType::GUI) {
|
||||
result += " -mwindows";
|
||||
result << "-mwindows";
|
||||
}
|
||||
|
||||
if (!mProject->options().linkerCmd.isEmpty()) {
|
||||
QString s = mProject->options().linkerCmd;
|
||||
if (!s.isEmpty()) {
|
||||
s.replace("_@@_", " ");
|
||||
QStringList params = textToLines(s);
|
||||
if (!params.isEmpty()) {
|
||||
foreach(const QString& param, params)
|
||||
result += " " + param;
|
||||
}
|
||||
s.replace("_@@_", " "); // historical reason
|
||||
result += parseArguments(s, {}, true);
|
||||
}
|
||||
}
|
||||
if (mProject->options().staticLink)
|
||||
result += " -static";
|
||||
result << "-static";
|
||||
} else {
|
||||
if (compilerSet()->staticLink()) {
|
||||
result += " -static";
|
||||
result << "-static";
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
QString Compiler::parseFileIncludesForAutolink(
|
||||
QStringList Compiler::parseFileIncludesForAutolink(
|
||||
const QString &filename,
|
||||
QSet<QString>& parsedFiles)
|
||||
{
|
||||
QString result;
|
||||
QStringList result;
|
||||
if (parsedFiles.contains(filename))
|
||||
return result;
|
||||
parsedFiles.insert(filename);
|
||||
PAutolink autolink = pAutolinkManager->getLink(filename);
|
||||
if (autolink) {
|
||||
result += ' '+autolink->linkOption;
|
||||
result += parseArgumentsWithoutVariables(autolink->linkOption);
|
||||
}
|
||||
QStringList includedFiles = mParserForFile->getFileDirectIncludes(filename);
|
||||
// log(QString("File %1 included:").arg(filename));
|
||||
|
@ -632,9 +626,7 @@ QString Compiler::parseFileIncludesForAutolink(
|
|||
|
||||
for (int i=includedFiles.size()-1;i>=0;i--) {
|
||||
QString includeFilename = includedFiles[i];
|
||||
result += parseFileIncludesForAutolink(
|
||||
includeFilename,
|
||||
parsedFiles);
|
||||
result += parseFileIncludesForAutolink(includeFilename, parsedFiles);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
@ -667,7 +659,7 @@ bool Compiler::parseForceUTF8ForAutolink(const QString &filename, QSet<QString>
|
|||
return false;
|
||||
}
|
||||
|
||||
void Compiler::runCommand(const QString &cmd, const QString &arguments, const QString &workingDir, const QByteArray& inputText, const QString& outputFile)
|
||||
void Compiler::runCommand(const QString &cmd, const QStringList &arguments, const QString &workingDir, const QByteArray& inputText, const QString& outputFile)
|
||||
{
|
||||
QProcess process;
|
||||
mStop = false;
|
||||
|
@ -692,7 +684,7 @@ void Compiler::runCommand(const QString &cmd, const QString &arguments, const Q
|
|||
env.insert("CFLAGS","");
|
||||
env.insert("CXXFLAGS","");
|
||||
process.setProcessEnvironment(env);
|
||||
process.setArguments(splitProcessCommand(arguments));
|
||||
process.setArguments(arguments);
|
||||
process.setWorkingDirectory(workingDir);
|
||||
QFile output;
|
||||
if (!outputFile.isEmpty()) {
|
||||
|
@ -773,6 +765,11 @@ void Compiler::runCommand(const QString &cmd, const QString &arguments, const Q
|
|||
output.close();
|
||||
}
|
||||
|
||||
QString Compiler::escapeCommandForLog(const QString &cmd, const QStringList &arguments)
|
||||
{
|
||||
return escapeCommandForPlatformShell(extractFileName(cmd), arguments);
|
||||
}
|
||||
|
||||
PCppParser Compiler::parser() const
|
||||
{
|
||||
return mParserForFile;
|
||||
|
|
|
@ -70,14 +70,14 @@ protected:
|
|||
virtual QByteArray pipedText();
|
||||
virtual bool prepareForRebuild() = 0;
|
||||
virtual bool beforeRunExtraCommand(int idx);
|
||||
virtual QString getCharsetArgument(const QByteArray& encoding, FileType fileType, bool onlyCheckSyntax);
|
||||
virtual QString getCCompileArguments(bool checkSyntax);
|
||||
virtual QString getCppCompileArguments(bool checkSyntax);
|
||||
virtual QString getCIncludeArguments();
|
||||
virtual QString getProjectIncludeArguments();
|
||||
virtual QString getCppIncludeArguments();
|
||||
virtual QString getLibraryArguments(FileType fileType);
|
||||
virtual QString parseFileIncludesForAutolink(
|
||||
virtual QStringList getCharsetArgument(const QByteArray& encoding, FileType fileType, bool onlyCheckSyntax);
|
||||
virtual QStringList getCCompileArguments(bool checkSyntax);
|
||||
virtual QStringList getCppCompileArguments(bool checkSyntax);
|
||||
virtual QStringList getCIncludeArguments();
|
||||
virtual QStringList getProjectIncludeArguments();
|
||||
virtual QStringList getCppIncludeArguments();
|
||||
virtual QStringList getLibraryArguments(FileType fileType);
|
||||
virtual QStringList parseFileIncludesForAutolink(
|
||||
const QString& filename,
|
||||
QSet<QString>& parsedFiles);
|
||||
virtual bool parseForceUTF8ForAutolink(
|
||||
|
@ -85,15 +85,16 @@ protected:
|
|||
QSet<QString>& parsedFiles);
|
||||
void log(const QString& msg);
|
||||
void error(const QString& msg);
|
||||
void runCommand(const QString& cmd, const QString& arguments, const QString& workingDir, const QByteArray& inputText=QByteArray(), const QString& outputFile=QString());
|
||||
void runCommand(const QString& cmd, const QStringList& arguments, const QString& workingDir, const QByteArray& inputText=QByteArray(), const QString& outputFile=QString());
|
||||
QString escapeCommandForLog(const QString &cmd, const QStringList &arguments);
|
||||
|
||||
protected:
|
||||
bool mOnlyCheckSyntax;
|
||||
QString mCompiler;
|
||||
QString mArguments;
|
||||
QStringList mExtraCompilersList;
|
||||
QStringList mExtraArgumentsList;
|
||||
QStringList mExtraOutputFilesList;
|
||||
QStringList mArguments;
|
||||
QList<QString> mExtraCompilersList;
|
||||
QList<QStringList> mExtraArgumentsList;
|
||||
QList<QString> mExtraOutputFilesList;
|
||||
QString mOutputFile;
|
||||
int mErrorCount;
|
||||
int mWarningCount;
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
#include "executablerunner.h"
|
||||
#include "ojproblemcasesrunner.h"
|
||||
#include "utils.h"
|
||||
#include "utils/parsearg.h"
|
||||
#include "../systemconsts.h"
|
||||
#include "../settings.h"
|
||||
#include <QMessageBox>
|
||||
|
@ -267,7 +268,7 @@ void CompilerManager::run(
|
|||
QString::number(consoleFlag),
|
||||
sharedMemoryId,
|
||||
localizePath(filename)
|
||||
} + splitProcessCommand(arguments);
|
||||
} + parseArgumentsWithoutVariables(arguments);
|
||||
if (pSettings->environment().useCustomTerminal()) {
|
||||
auto [filename, args, fileOwner] = wrapCommandForTerminalEmulator(
|
||||
pSettings->environment().terminalPath(),
|
||||
|
@ -285,7 +286,7 @@ void CompilerManager::run(
|
|||
}
|
||||
} else {
|
||||
//delete when thread finished
|
||||
execRunner = new ExecutableRunner(filename,splitProcessCommand(arguments),workDir);
|
||||
execRunner = new ExecutableRunner(filename, parseArgumentsWithoutVariables(arguments), workDir);
|
||||
}
|
||||
#else
|
||||
QStringList execArgs;
|
||||
|
@ -310,19 +311,19 @@ void CompilerManager::run(
|
|||
sharedMemoryId,
|
||||
redirectInputFilename,
|
||||
localizePath(filename),
|
||||
} + splitProcessCommand(arguments);
|
||||
} + parseArgumentsWithoutVariables(arguments);
|
||||
} else {
|
||||
execArgs = QStringList{
|
||||
consolePauserPath,
|
||||
QString::number(consoleFlag),
|
||||
sharedMemoryId,
|
||||
localizePath(filename),
|
||||
} + splitProcessCommand(arguments);
|
||||
} + parseArgumentsWithoutVariables(arguments);
|
||||
}
|
||||
} else {
|
||||
execArgs = QStringList{
|
||||
localizePath(filename),
|
||||
} + splitProcessCommand(arguments);
|
||||
} + parseArgumentsWithoutVariables(arguments);
|
||||
}
|
||||
auto [filename, args, fileOwner] = wrapCommandForTerminalEmulator(
|
||||
pSettings->environment().terminalPath(),
|
||||
|
@ -336,7 +337,7 @@ void CompilerManager::run(
|
|||
execRunner->setStartConsole(true);
|
||||
} else {
|
||||
//delete when thread finished
|
||||
execRunner = new ExecutableRunner(filename,splitProcessCommand(arguments),workDir);
|
||||
execRunner = new ExecutableRunner(filename, parseArgumentsWithoutVariables(arguments), workDir);
|
||||
}
|
||||
if (redirectInput) {
|
||||
execRunner->setRedirectInput(true);
|
||||
|
@ -380,7 +381,7 @@ void CompilerManager::doRunProblem(const QString &filename, const QString &argum
|
|||
if (mRunner!=nullptr) {
|
||||
return;
|
||||
}
|
||||
OJProblemCasesRunner * execRunner = new OJProblemCasesRunner(filename,splitProcessCommand(arguments),workDir,problemCases);
|
||||
OJProblemCasesRunner * execRunner = new OJProblemCasesRunner(filename, parseArgumentsWithoutVariables(arguments), workDir, problemCases);
|
||||
mRunner = execRunner;
|
||||
if (pSettings->executor().enableCaseLimit()) {
|
||||
execRunner->setExecTimeout(pSettings->executor().caseTimeout());
|
||||
|
|
|
@ -66,20 +66,20 @@ bool FileCompiler::prepareForCompile()
|
|||
log(tr("- Compiler Set Name: %1").arg(compilerSet()->name()));
|
||||
log("");
|
||||
FileType fileType = getFileType(mFilename);
|
||||
mArguments = QString(" \"%1\"").arg(mFilename);
|
||||
mArguments = QStringList{mFilename};
|
||||
if (!mOnlyCheckSyntax) {
|
||||
switch(compilerSet()->compilationStage()) {
|
||||
case Settings::CompilerSet::CompilationStage::PreprocessingOnly:
|
||||
mOutputFile=changeFileExt(mFilename,compilerSet()->preprocessingSuffix());
|
||||
mArguments+=" -E";
|
||||
mArguments << "-E";
|
||||
break;
|
||||
case Settings::CompilerSet::CompilationStage::CompilationProperOnly:
|
||||
mOutputFile=changeFileExt(mFilename,compilerSet()->compilationProperSuffix());
|
||||
mArguments+=" -S -fverbose-asm";
|
||||
mArguments += {"-S", "-fverbose-asm"};
|
||||
break;
|
||||
case Settings::CompilerSet::CompilationStage::AssemblingOnly:
|
||||
mOutputFile=changeFileExt(mFilename,compilerSet()->assemblingSuffix());
|
||||
mArguments+=" -c";
|
||||
mArguments << "-c";
|
||||
break;
|
||||
case Settings::CompilerSet::CompilationStage::GenerateExecutable:
|
||||
mOutputFile = changeFileExt(mFilename,compilerSet()->executableSuffix());
|
||||
|
@ -91,14 +91,14 @@ bool FileCompiler::prepareForCompile()
|
|||
}
|
||||
}
|
||||
#endif
|
||||
mArguments+=QString(" -o \"%1\"").arg(mOutputFile);
|
||||
mArguments += {"-o", mOutputFile};
|
||||
|
||||
#if defined(ARCH_X86_64) || defined(ARCH_X86)
|
||||
if (mCompileType == CppCompileType::GenerateAssemblyOnly) {
|
||||
if (pSettings->languages().noSEHDirectivesWhenGenerateASM())
|
||||
mArguments+=" -fno-asynchronous-unwind-tables";
|
||||
mArguments << "-fno-asynchronous-unwind-tables";
|
||||
if (pSettings->languages().x86DialectOfASMGenerated()==Settings::Languages::X86ASMDialect::Intel)
|
||||
mArguments+=" -masm=intel";
|
||||
mArguments << "-masm=intel";
|
||||
}
|
||||
#endif
|
||||
//remove the old file if it exists
|
||||
|
@ -171,7 +171,7 @@ bool FileCompiler::prepareForCompile()
|
|||
break;
|
||||
}
|
||||
if (hasStart) {
|
||||
mArguments+=" -nostartfiles";
|
||||
mArguments << "-nostartfiles";
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -185,7 +185,8 @@ bool FileCompiler::prepareForCompile()
|
|||
log(tr("Processing %1 source file:").arg(strFileType));
|
||||
log("------------------");
|
||||
log(tr("%1 Compiler: %2").arg(strFileType).arg(mCompiler));
|
||||
log(tr("Command: %1 %2").arg(extractFileName(mCompiler)).arg(mArguments));
|
||||
QString command = escapeCommandForLog(mCompiler, mArguments);
|
||||
log(tr("Command: %1").arg(command));
|
||||
mDirectory = extractFileDir(mFilename);
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -20,6 +20,10 @@
|
|||
#include "../systemconsts.h"
|
||||
#include "qt_utils/charsetinfo.h"
|
||||
#include "../editor.h"
|
||||
#include "qt_utils/utils.h"
|
||||
#include "utils.h"
|
||||
#include "utils/escape.h"
|
||||
#include "utils/parsearg.h"
|
||||
|
||||
#include <QDir>
|
||||
|
||||
|
@ -51,12 +55,15 @@ void ProjectCompiler::createStandardMakeFile()
|
|||
{
|
||||
QFile file(mProject->makeFileName());
|
||||
newMakeFile(file);
|
||||
file.write("$(BIN): $(OBJ)\n");
|
||||
QString executable = extractRelativePath(mProject->makeFileName(), mProject->executable());
|
||||
QString exeTarget = escapeFilenameForMakefileTarget(executable);
|
||||
QString exeCommand = escapeArgumentForMakefileRecipe(executable, false);
|
||||
writeln(file, exeTarget + ": $(OBJ)\n");
|
||||
if (!mOnlyCheckSyntax) {
|
||||
if (mProject->options().isCpp) {
|
||||
writeln(file,"\t$(CPP) $(LINKOBJ) -o $(BIN) $(LIBS)");
|
||||
writeln(file, "\t$(CXX) $(LINKOBJ) -o " + exeCommand + " $(LIBS)");
|
||||
} else
|
||||
writeln(file,"\t$(CC) $(LINKOBJ) -o $(BIN) $(LIBS)");
|
||||
writeln(file, "\t$(CC) $(LINKOBJ) -o " + exeCommand + " $(LIBS)");
|
||||
}
|
||||
writeMakeObjFilesRules(file);
|
||||
}
|
||||
|
@ -65,9 +72,12 @@ void ProjectCompiler::createStaticMakeFile()
|
|||
{
|
||||
QFile file(mProject->makeFileName());
|
||||
newMakeFile(file);
|
||||
writeln(file,"$(BIN): $(LINKOBJ)");
|
||||
writeln(file,"\tar r $(BIN) $(LINKOBJ)");
|
||||
writeln(file,"\tranlib $(BIN)");
|
||||
QString executable = extractRelativePath(mProject->makeFileName(), mProject->executable());
|
||||
QString exeTarget = escapeFilenameForMakefileTarget(executable);
|
||||
QString exeCommand = escapeArgumentForMakefileRecipe(executable, false);
|
||||
writeln(file, exeTarget + ": $(LINKOBJ)");
|
||||
writeln(file, "\tar r " + exeCommand + " $(LINKOBJ)");
|
||||
writeln(file, "\tranlib " + exeCommand);
|
||||
writeMakeObjFilesRules(file);
|
||||
}
|
||||
|
||||
|
@ -75,11 +85,14 @@ void ProjectCompiler::createDynamicMakeFile()
|
|||
{
|
||||
QFile file(mProject->makeFileName());
|
||||
newMakeFile(file);
|
||||
writeln(file,"$(BIN): $(LINKOBJ)");
|
||||
QString executable = extractRelativePath(mProject->makeFileName(), mProject->executable());
|
||||
QString exeTarget = escapeFilenameForMakefileTarget(executable);
|
||||
QString exeCommand = escapeArgumentForMakefileRecipe(executable, false);
|
||||
writeln(file, exeTarget + ": $(LINKOBJ)");
|
||||
if (mProject->options().isCpp) {
|
||||
writeln(file, "\t$(CPP) -mdll $(LINKOBJ) -o $(BIN) $(LIBS) -Wl,--output-def,$(DEF),--out-implib,$(STATIC)");
|
||||
writeln(file, "\t$(CXX) -mdll $(LINKOBJ) -o " + exeCommand + " $(LIBS) -Wl,--output-def,$(DEF),--out-implib,$(STATIC)");
|
||||
} else {
|
||||
writeln(file, "\t$(CC) -mdll $(LINKOBJ) -o $(BIN) $(LIBS) -Wl,--output-def,$(DEF),--out-implib,$(STATIC)");
|
||||
writeln(file, "\t$(CC) -mdll $(LINKOBJ) -o " + exeCommand + " $(LIBS) -Wl,--output-def,$(DEF),--out-implib,$(STATIC)");
|
||||
}
|
||||
writeMakeObjFilesRules(file);
|
||||
}
|
||||
|
@ -121,8 +134,13 @@ void ProjectCompiler::newMakeFile(QFile& file)
|
|||
// PCH
|
||||
if (mProject->options().usePrecompiledHeader
|
||||
&& fileExists(mProject->options().precompiledHeader)) {
|
||||
writeln(file, "$(PCH) : $(PCH_H)");
|
||||
writeln(file, "\t$(CPP) -c $(PCH_H) -o $(PCH) $(CXXFLAGS)");
|
||||
QString pchH = extractRelativePath(mProject->makeFileName(), mProject->options().precompiledHeader);
|
||||
QString pchHCommand = escapeArgumentForMakefileRecipe(pchH, false);
|
||||
QString pch = extractRelativePath(mProject->makeFileName(), mProject->options().precompiledHeader + "." GCH_EXT);
|
||||
QString pchTarget = escapeFilenameForMakefileTarget(pch);
|
||||
QString pchCommand = escapeArgumentForMakefileRecipe(pch, false);
|
||||
writeln(file, pchTarget + ": $(PCH_H)");
|
||||
writeln(file, "\t$(CXX) -c " + pchHCommand + " -o " + pchCommand + " $(CXXFLAGS)");
|
||||
writeln(file);
|
||||
}
|
||||
}
|
||||
|
@ -137,9 +155,9 @@ void ProjectCompiler::writeMakeHeader(QFile &file)
|
|||
void ProjectCompiler::writeMakeDefines(QFile &file)
|
||||
{
|
||||
// Get list of object files
|
||||
QString Objects;
|
||||
QString LinkObjects;
|
||||
QString cleanObjects;
|
||||
QStringList Objects;
|
||||
QStringList LinkObjects;
|
||||
QStringList cleanObjects;
|
||||
|
||||
// Create a list of object files
|
||||
foreach(const PProjectUnit &unit, mProject->unitList()) {
|
||||
|
@ -157,35 +175,22 @@ void ProjectCompiler::writeMakeDefines(QFile &file)
|
|||
QString fullObjFile = includeTrailingPathDelimiter(mProject->options().objectOutput)
|
||||
+ extractFileName(unit->fileName());
|
||||
QString relativeObjFile = extractRelativePath(mProject->directory(), changeFileExt(fullObjFile, OBJ_EXT));
|
||||
QString objFile = genMakePath2(relativeObjFile);
|
||||
Objects += ' ' + objFile;
|
||||
#ifdef Q_OS_WIN
|
||||
cleanObjects += ' ' + genMakePath1(relativeObjFile).replace("/",QDir::separator());
|
||||
#else
|
||||
cleanObjects += ' ' + genMakePath1(relativeObjFile);
|
||||
#endif
|
||||
Objects << relativeObjFile;
|
||||
cleanObjects << localizePath(relativeObjFile);
|
||||
if (unit->link()) {
|
||||
LinkObjects += ' ' + genMakePath1(relativeObjFile);
|
||||
LinkObjects << relativeObjFile;
|
||||
}
|
||||
} else {
|
||||
Objects += ' ' + genMakePath2(changeFileExt(RelativeName, OBJ_EXT));
|
||||
#ifdef Q_OS_WIN
|
||||
cleanObjects += ' ' + genMakePath1(changeFileExt(RelativeName, OBJ_EXT)).replace("/",QDir::separator());
|
||||
#else
|
||||
cleanObjects += ' ' + genMakePath1(changeFileExt(RelativeName, OBJ_EXT));
|
||||
#endif
|
||||
Objects << changeFileExt(RelativeName, OBJ_EXT);
|
||||
cleanObjects << localizePath(changeFileExt(RelativeName, OBJ_EXT));
|
||||
if (unit->link())
|
||||
LinkObjects = LinkObjects + ' ' + genMakePath1(changeFileExt(RelativeName, OBJ_EXT));
|
||||
LinkObjects << changeFileExt(RelativeName, OBJ_EXT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Objects = Objects.trimmed();
|
||||
LinkObjects = LinkObjects.trimmed();
|
||||
|
||||
// Get windres file
|
||||
QString objResFile;
|
||||
QString objResFile2;
|
||||
QString cleanRes;
|
||||
#ifdef Q_OS_WIN
|
||||
if (!mProject->options().privateResource.isEmpty()) {
|
||||
|
@ -194,86 +199,90 @@ void ProjectCompiler::writeMakeDefines(QFile &file)
|
|||
changeFileExt(mProject->options().privateResource, RES_EXT);
|
||||
|
||||
QString relativeResFile = extractRelativePath(mProject->directory(), fullResFile);
|
||||
objResFile = genMakePath1(relativeResFile);
|
||||
objResFile2 = genMakePath2(relativeResFile);
|
||||
cleanRes += ' ' + genMakePath1(changeFileExt(relativeResFile, RES_EXT)).replace("/",QDir::separator());
|
||||
objResFile = relativeResFile;
|
||||
cleanRes = localizePath(changeFileExt(relativeResFile, RES_EXT));
|
||||
} else {
|
||||
objResFile = genMakePath1(changeFileExt(mProject->options().privateResource, RES_EXT));
|
||||
objResFile2 = genMakePath2(changeFileExt(mProject->options().privateResource, RES_EXT));
|
||||
cleanRes += ' ' + genMakePath1(changeFileExt(mProject->options().privateResource, RES_EXT)).replace("/",QDir::separator());
|
||||
objResFile = changeFileExt(mProject->options().privateResource, RES_EXT);
|
||||
cleanRes = localizePath(changeFileExt(mProject->options().privateResource, RES_EXT));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// Mention progress in the logs
|
||||
if (!objResFile.isEmpty()) {
|
||||
log(tr("- Resource File: %1").arg(generateAbsolutePath(mProject->directory(),objResFile)));
|
||||
QString absolutePath = generateAbsolutePath(mProject->directory(),objResFile);
|
||||
QString escaped = escapeArgumentForPlatformShell(absolutePath, false);
|
||||
log(tr("- Resource File: %1").arg(escaped));
|
||||
}
|
||||
log("");
|
||||
|
||||
QString cxx = extractFileName(compilerSet()->cppCompiler());
|
||||
QString cc = extractFileName(compilerSet()->CCompiler());
|
||||
#ifdef Q_OS_WIN
|
||||
QString windres = extractFileName(compilerSet()->resourceCompiler());
|
||||
#endif
|
||||
|
||||
// Get list of applicable flags
|
||||
QString cCompileArguments = getCCompileArguments(false);
|
||||
QString cppCompileArguments = getCppCompileArguments(false);
|
||||
QString libraryArguments = getLibraryArguments(FileType::Project);
|
||||
QString cIncludeArguments = getCIncludeArguments() + " " + getProjectIncludeArguments();
|
||||
QString cppIncludeArguments = getCppIncludeArguments() + " " +getProjectIncludeArguments();
|
||||
|
||||
if (cCompileArguments.indexOf(" -g3")>=0
|
||||
|| cCompileArguments.startsWith("-g3")) {
|
||||
cCompileArguments += " -D__DEBUG__";
|
||||
cppCompileArguments+= " -D__DEBUG__";
|
||||
QStringList cCompileArguments = getCCompileArguments(false);
|
||||
QStringList cxxCompileArguments = getCppCompileArguments(false);
|
||||
if (cCompileArguments.contains("-g3")) {
|
||||
cCompileArguments << "-D__DEBUG__";
|
||||
cxxCompileArguments << "-D__DEBUG__";
|
||||
}
|
||||
QStringList libraryArguments = getLibraryArguments(FileType::Project);
|
||||
QStringList cIncludeArguments = getCIncludeArguments();
|
||||
QStringList cxxIncludeArguments = getCppIncludeArguments();
|
||||
#ifdef Q_OS_WIN
|
||||
QStringList resourceArguments = parseArguments(mProject->options().resourceCmd, devCppMacroVariables(), true);
|
||||
#endif
|
||||
|
||||
writeln(file,"CPP = " + extractFileName(compilerSet()->cppCompiler()));
|
||||
writeln(file,"CC = " + extractFileName(compilerSet()->CCompiler()));
|
||||
QString executable = extractRelativePath(mProject->makeFileName(), mProject->executable());
|
||||
QString cleanExe = localizePath(executable);
|
||||
QString pchH = extractRelativePath(mProject->makeFileName(), mProject->options().precompiledHeader);
|
||||
QString pch = extractRelativePath(mProject->makeFileName(), mProject->options().precompiledHeader + "." GCH_EXT);
|
||||
|
||||
// programs
|
||||
writeln(file, "CXX = " + escapeArgumentForMakefileVariableValue(cxx, true));
|
||||
writeln(file, "CC = " + escapeArgumentForMakefileVariableValue(cc, true));
|
||||
#ifdef Q_OS_WIN
|
||||
writeln(file,"WINDRES = " + extractFileName(compilerSet()->resourceCompiler()));
|
||||
writeln(file, "WINDRES = " + escapeArgumentForMakefileVariableValue(windres, true));
|
||||
#endif
|
||||
writeln(file, "RM = " CLEAN_PROGRAM);
|
||||
|
||||
// compiler flags
|
||||
writeln(file, "LIBS = " + escapeArgumentsForMakefileVariableValue(libraryArguments));
|
||||
writeln(file, "INCS = " + escapeArgumentsForMakefileVariableValue(cIncludeArguments));
|
||||
writeln(file, "CXXINCS = " + escapeArgumentsForMakefileVariableValue(cxxIncludeArguments));
|
||||
writeln(file, "CXXFLAGS = $(CXXINCS) " + escapeArgumentsForMakefileVariableValue(cxxCompileArguments));
|
||||
writeln(file, "CFLAGS = $(INCS) " + escapeArgumentsForMakefileVariableValue(cCompileArguments));
|
||||
#ifdef Q_OS_WIN
|
||||
writeln(file, "WINDRESFLAGS = " + escapeArgumentsForMakefileVariableValue(resourceArguments));
|
||||
#endif
|
||||
|
||||
// objects referenced in prerequisites
|
||||
// do not use them in targets or command arguments, they have different escaping rules
|
||||
if (!objResFile.isEmpty()) {
|
||||
writeln(file,"RES = " + objResFile2);
|
||||
writeln(file,"OBJ = " + Objects + " $(RES)");
|
||||
writeln(file,"LINKOBJ = " + LinkObjects + " " + objResFile);
|
||||
#ifdef Q_OS_WIN
|
||||
writeln(file,"CLEANOBJ = " + cleanObjects +
|
||||
" " + cleanRes
|
||||
+ " " + genMakePath1(extractRelativePath(mProject->makeFileName(), mProject->executable())).replace("/",QDir::separator()) );
|
||||
#else
|
||||
writeln(file,"CLEANOBJ = " + cleanObjects +
|
||||
" " + cleanRes
|
||||
+ " " + genMakePath1(extractRelativePath(mProject->makeFileName(), mProject->executable())));
|
||||
#endif
|
||||
writeln(file, "RES = " + escapeFilenameForMakefilePrerequisite(objResFile));
|
||||
writeln(file, "OBJ = " + escapeFilenamesForMakefilePrerequisite(Objects) + " $(RES)");
|
||||
} else {
|
||||
writeln(file,"OBJ = " + Objects);
|
||||
writeln(file,"LINKOBJ = " + LinkObjects);
|
||||
#ifdef Q_OS_WIN
|
||||
writeln(file,"CLEANOBJ = " + cleanObjects +
|
||||
+ " " + genMakePath1(extractRelativePath(mProject->makeFileName(), mProject->executable())).replace("/",QDir::separator()) );
|
||||
#else
|
||||
writeln(file,"CLEANOBJ = " + cleanObjects +
|
||||
+ " " + genMakePath1(extractRelativePath(mProject->makeFileName(), mProject->executable())));
|
||||
#endif
|
||||
writeln(file, "OBJ = " + escapeFilenamesForMakefilePrerequisite(Objects));
|
||||
};
|
||||
libraryArguments.replace('\\', '/');
|
||||
writeln(file,"LIBS = " + libraryArguments);
|
||||
cIncludeArguments.replace('\\', '/');
|
||||
writeln(file,"INCS = " + cIncludeArguments);
|
||||
cppIncludeArguments.replace('\\', '/');
|
||||
writeln(file,"CXXINCS = " + cppIncludeArguments);
|
||||
writeln(file,"BIN = " + genMakePath1(extractRelativePath(mProject->makeFileName(), mProject->executable())));
|
||||
cppCompileArguments.replace('\\', '/');
|
||||
writeln(file,"CXXFLAGS = $(CXXINCS) " + cppCompileArguments);
|
||||
//writeln(file,"ENCODINGS = -finput-charset=utf-8 -fexec-charset='+GetSystemCharsetName);
|
||||
cCompileArguments.replace('\\', '/');
|
||||
writeln(file,"CFLAGS = $(INCS) " + cCompileArguments);
|
||||
writeln(file, QString("RM = ") + CLEAN_PROGRAM );
|
||||
writeln(file, "BIN = " + escapeFilenameForMakefilePrerequisite(executable));
|
||||
if (mProject->options().usePrecompiledHeader
|
||||
&& fileExists(mProject->options().precompiledHeader)){
|
||||
writeln(file,"PCH_H = " + genMakePath1(extractRelativePath(mProject->makeFileName(), mProject->options().precompiledHeader )));
|
||||
writeln(file,"PCH = " + genMakePath1(extractRelativePath(mProject->makeFileName(), mProject->options().precompiledHeader+"."+GCH_EXT)));
|
||||
writeln(file, "PCH_H = " + escapeFilenameForMakefilePrerequisite(pchH));
|
||||
writeln(file, "PCH = " + escapeFilenameForMakefilePrerequisite(pch));
|
||||
}
|
||||
#ifdef Q_OS_WIN
|
||||
writeln(file,"WINDRESFLAGS = " + mProject->options().resourceCmd);
|
||||
#endif
|
||||
|
||||
// object referenced in command arguments
|
||||
// use them in targets or prerequisites, they have different escaping rules
|
||||
if (!objResFile.isEmpty()) {
|
||||
writeln(file, "LINKOBJ = " + escapeArgumentsForMakefileVariableValue(LinkObjects) + " " + escapeArgumentForMakefileVariableValue(objResFile, false));
|
||||
writeln(file, "CLEANOBJ = " + escapeArgumentsForMakefileVariableValue(cleanObjects) + " " + escapeArgumentForMakefileVariableValue(cleanRes, false) + " " + escapeArgumentForMakefileVariableValue(cleanExe, false));
|
||||
} else {
|
||||
writeln(file, "LINKOBJ = " + escapeArgumentsForMakefileVariableValue(LinkObjects));
|
||||
writeln(file, "CLEANOBJ = " + escapeArgumentsForMakefileVariableValue(cleanObjects) + " " + escapeArgumentForMakefileVariableValue(cleanExe, false));
|
||||
};
|
||||
|
||||
// This needs to be put in before the clean command.
|
||||
if (mProject->options().type == ProjectType::DynamicLib) {
|
||||
|
@ -290,15 +299,12 @@ void ProjectCompiler::writeMakeDefines(QFile &file)
|
|||
libOutputFile = extractFileName(libOutputFile);
|
||||
else
|
||||
libOutputFile = extractRelativePath(mProject->makeFileName(), libOutputFile);
|
||||
writeln(file,"DEF = " + genMakePath1(changeFileExt(libOutputFile, DEF_EXT)));
|
||||
writeln(file,"STATIC = " + genMakePath1(changeFileExt(libOutputFile, LIB_EXT)));
|
||||
#ifdef Q_OS_WIN
|
||||
writeln(file,"CLEAN_DEF = " + genMakePath1(changeFileExt(libOutputFile, DEF_EXT)).replace("/",QDir::separator()));
|
||||
writeln(file,"CLEAN_STATIC = " + genMakePath1(changeFileExt(libOutputFile, LIB_EXT)).replace("/",QDir::separator()));
|
||||
#else
|
||||
writeln(file,"CLEAN_DEF = " + genMakePath1(changeFileExt(libOutputFile, DEF_EXT)));
|
||||
writeln(file,"CLEAN_STATIC = " + genMakePath1(changeFileExt(libOutputFile, LIB_EXT)));
|
||||
#endif
|
||||
|
||||
QString defFile = localizePath(changeFileExt(libOutputFile, DEF_EXT));
|
||||
QString staticFile = localizePath(changeFileExt(libOutputFile, LIB_EXT));
|
||||
|
||||
writeln(file,"DEF = " + escapeArgumentForMakefileVariableValue(defFile, false));
|
||||
writeln(file,"STATIC = " + escapeArgumentForMakefileVariableValue(staticFile, false));
|
||||
}
|
||||
writeln(file);
|
||||
}
|
||||
|
@ -315,7 +321,7 @@ void ProjectCompiler::writeMakeTarget(QFile &file)
|
|||
void ProjectCompiler::writeMakeIncludes(QFile &file)
|
||||
{
|
||||
foreach(const QString& s, mProject->options().makeIncludes) {
|
||||
writeln(file, "include " + genMakePath1(s));
|
||||
writeln(file, "include " + escapeFilenameForMakefileInclude(s));
|
||||
}
|
||||
if (!mProject->options().makeIncludes.isEmpty()) {
|
||||
writeln(file);
|
||||
|
@ -328,11 +334,12 @@ void ProjectCompiler::writeMakeClean(QFile &file)
|
|||
QString target="$(CLEANOBJ)";
|
||||
if (mProject->options().usePrecompiledHeader
|
||||
&& fileExists(mProject->options().precompiledHeader)) {
|
||||
target += " $(PCH)";
|
||||
QString pch = extractRelativePath(mProject->makeFileName(), mProject->options().precompiledHeader + "." GCH_EXT);
|
||||
target += ' ' + escapeArgumentForMakefileRecipe(pch, false);
|
||||
}
|
||||
|
||||
if (mProject->options().type == ProjectType::DynamicLib) {
|
||||
target +=" $(CLEAN_DEF) $(CLEAN_STATIC)";
|
||||
target +=" $(DEF) $(STATIC)";
|
||||
}
|
||||
writeln(file, QString("\t-$(RM) %1 > %2 2>&1").arg(target,NULL_FILE));
|
||||
writeln(file);
|
||||
|
@ -356,7 +363,7 @@ void ProjectCompiler::writeMakeObjFilesRules(QFile &file)
|
|||
QString shortFileName = extractRelativePath(mProject->makeFileName(),unit->fileName());
|
||||
|
||||
writeln(file);
|
||||
QString objStr=genMakePath2(shortFileName);
|
||||
QString objStr = escapeFilenameForMakefilePrerequisite(shortFileName);
|
||||
// if we have scanned it, use scanned info
|
||||
if (parser && parser->fileScanned(unit->fileName())) {
|
||||
QSet<QString> fileIncludes = parser->getIncludedFiles(unit->fileName());
|
||||
|
@ -367,33 +374,36 @@ void ProjectCompiler::writeMakeObjFilesRules(QFile &file)
|
|||
if (mProject->options().usePrecompiledHeader &&
|
||||
unit2->fileName() == mProject->options().precompiledHeader)
|
||||
precompileStr = " $(PCH) ";
|
||||
else
|
||||
objStr = objStr + ' ' + genMakePath2(extractRelativePath(mProject->makeFileName(),unit2->fileName()));
|
||||
else {
|
||||
QString prereq = extractRelativePath(mProject->makeFileName(), unit2->fileName());
|
||||
objStr = objStr + ' ' + escapeFilenameForMakefilePrerequisite(prereq);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
foreach(const PProjectUnit &unit2, projectUnits) {
|
||||
FileType fileType = getFileType(unit2->fileName());
|
||||
if (fileType == FileType::CHeader || fileType==FileType::CppHeader)
|
||||
objStr = objStr + ' ' + genMakePath2(extractRelativePath(mProject->makeFileName(),unit2->fileName()));
|
||||
if (fileType == FileType::CHeader || fileType==FileType::CppHeader) {
|
||||
QString prereq = extractRelativePath(mProject->makeFileName(), unit2->fileName());
|
||||
objStr = objStr + ' ' + escapeFilenameForMakefilePrerequisite(prereq);
|
||||
}
|
||||
}
|
||||
QString objFileName;
|
||||
QString objFileName2;
|
||||
}
|
||||
QString objFileNameTarget;
|
||||
QString objFileNameCommand;
|
||||
if (!mProject->options().objectOutput.isEmpty()) {
|
||||
QString fullObjname = includeTrailingPathDelimiter(mProject->options().objectOutput) +
|
||||
extractFileName(unit->fileName());
|
||||
objFileName = genMakePath2(extractRelativePath(mProject->makeFileName(), changeFileExt(fullObjname, OBJ_EXT)));
|
||||
objFileName2 = genMakePath1(extractRelativePath(mProject->makeFileName(), changeFileExt(fullObjname, OBJ_EXT)));
|
||||
// if (!extractFileDir(ObjFileName).isEmpty()) {
|
||||
// objStr = genMakePath2(includeTrailingPathDelimiter(extractFileDir(ObjFileName))) + objStr;
|
||||
// }
|
||||
QString objectFile = extractRelativePath(mProject->makeFileName(), changeFileExt(fullObjname, OBJ_EXT));
|
||||
objFileNameTarget = escapeFilenameForMakefileTarget(objectFile);
|
||||
objFileNameCommand = escapeArgumentForMakefileRecipe(objectFile, false);
|
||||
} else {
|
||||
objFileName = genMakePath2(changeFileExt(shortFileName, OBJ_EXT));
|
||||
objFileName2 = genMakePath1(changeFileExt(shortFileName, OBJ_EXT));
|
||||
QString objectFile = changeFileExt(shortFileName, OBJ_EXT);
|
||||
objFileNameTarget = escapeFilenameForMakefileTarget(objectFile);
|
||||
objFileNameCommand = escapeArgumentForMakefileRecipe(objectFile, false);
|
||||
}
|
||||
|
||||
objStr = objFileName + ": "+objStr+precompileStr;
|
||||
objStr = objFileNameTarget + ": " + objStr + precompileStr;
|
||||
|
||||
writeln(file,objStr);
|
||||
|
||||
|
@ -457,11 +467,11 @@ void ProjectCompiler::writeMakeObjFilesRules(QFile &file)
|
|||
|
||||
if (fileType==FileType::CSource || fileType==FileType::CppSource) {
|
||||
if (unit->compileCpp())
|
||||
writeln(file, "\t$(CPP) -c " + genMakePath1(shortFileName) + " -o " + objFileName2 + " $(CXXFLAGS) " + encodingStr);
|
||||
writeln(file, "\t$(CXX) -c " + escapeArgumentForMakefileRecipe(shortFileName, false) + " -o " + objFileNameCommand + " $(CXXFLAGS) " + encodingStr);
|
||||
else
|
||||
writeln(file, "\t$(CC) -c " + genMakePath1(shortFileName) + " -o " + objFileName2 + " $(CFLAGS) " + encodingStr);
|
||||
writeln(file, "\t$(CC) -c " + escapeArgumentForMakefileRecipe(shortFileName, false) + " -o " + objFileNameCommand + " $(CFLAGS) " + encodingStr);
|
||||
} else if (fileType==FileType::GAS) {
|
||||
writeln(file, "\t$(CC) -c " + genMakePath1(shortFileName) + " -o " + objFileName2 + " $(CFLAGS) " + encodingStr);
|
||||
writeln(file, "\t$(CC) -c " + escapeArgumentForMakefileRecipe(shortFileName, false) + " -o " + objFileNameCommand + " $(CFLAGS) " + encodingStr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -473,7 +483,7 @@ void ProjectCompiler::writeMakeObjFilesRules(QFile &file)
|
|||
for (int i=0;i<mProject->options().resourceIncludes.count();i++) {
|
||||
QString filename = mProject->options().resourceIncludes[i];
|
||||
if (!filename.isEmpty())
|
||||
ResIncludes = ResIncludes + " --include-dir " + genMakePath1(filename);
|
||||
ResIncludes += " --include-dir " + escapeArgumentForMakefileRecipe(filename, false);
|
||||
}
|
||||
|
||||
QString resFiles;
|
||||
|
@ -483,10 +493,9 @@ void ProjectCompiler::writeMakeObjFilesRules(QFile &file)
|
|||
continue;
|
||||
if (fileExists(unit->fileName())) {
|
||||
QString ResFile = extractRelativePath(mProject->makeFileName(), unit->fileName());
|
||||
resFiles = resFiles + genMakePath2(ResFile) + ' ';
|
||||
resFiles = resFiles + escapeFilenameForMakefilePrerequisite(ResFile) + ' ';
|
||||
}
|
||||
}
|
||||
resFiles = resFiles.trimmed();
|
||||
|
||||
// Determine resource output file
|
||||
QString fullName;
|
||||
|
@ -496,10 +505,12 @@ void ProjectCompiler::writeMakeObjFilesRules(QFile &file)
|
|||
} else {
|
||||
fullName = changeFileExt(mProject->options().privateResource, RES_EXT);
|
||||
}
|
||||
QString objFileName = genMakePath1(extractRelativePath(mProject->filename(), fullName));
|
||||
QString objFileName2 = genMakePath2(extractRelativePath(mProject->filename(), fullName));
|
||||
QString privResName = genMakePath1(extractRelativePath(mProject->filename(), mProject->options().privateResource));
|
||||
QString privResName2 = genMakePath2(extractRelativePath(mProject->filename(), mProject->options().privateResource));
|
||||
QString objFile = extractRelativePath(mProject->filename(), fullName);
|
||||
QString objFileNameCommand = escapeArgumentForMakefileRecipe(objFile, false);
|
||||
QString objFileNameTarget = escapeFilenameForMakefileTarget(objFile);
|
||||
QString privRes = extractRelativePath(mProject->filename(), mProject->options().privateResource);
|
||||
QString privResNameCommand = escapeArgumentForMakefileRecipe(privRes, false);
|
||||
QString privResNamePrereq = escapeFilenameForMakefilePrerequisite(privRes);
|
||||
|
||||
// Build final cmd
|
||||
QString windresArgs;
|
||||
|
@ -508,8 +519,8 @@ void ProjectCompiler::writeMakeObjFilesRules(QFile &file)
|
|||
windresArgs = " -F pe-i386";
|
||||
|
||||
writeln(file);
|
||||
writeln(file, objFileName2 + ": " + privResName2 + ' ' + resFiles);
|
||||
writeln(file, "\t$(WINDRES) -i " + privResName + windresArgs + " --input-format=rc -o " + objFileName + " -O coff $(WINDRESFLAGS)"
|
||||
writeln(file, objFileNameTarget + ": " + privResNamePrereq + ' ' + resFiles);
|
||||
writeln(file, "\t$(WINDRES) -i " + privResNameCommand + windresArgs + " --input-format=rc -o " + objFileNameCommand + " -O coff $(WINDRESFLAGS)"
|
||||
+ ResIncludes);
|
||||
writeln(file);
|
||||
}
|
||||
|
@ -568,35 +579,39 @@ bool ProjectCompiler::prepareForCompile()
|
|||
} else {
|
||||
parallelParam = QString("-j%1").arg(mProject->options().parellelBuildingJobs);
|
||||
}
|
||||
} else {
|
||||
parallelParam = "-j1";
|
||||
}
|
||||
|
||||
QString makefile = extractRelativePath(mProject->directory(), mProject->makeFileName());
|
||||
QStringList cleanArgs{
|
||||
"-f",
|
||||
makefile,
|
||||
"clean",
|
||||
};
|
||||
QStringList makeAllArgs{
|
||||
parallelParam,
|
||||
"-f",
|
||||
makefile,
|
||||
"all",
|
||||
};
|
||||
if (mOnlyClean) {
|
||||
mArguments = QString(" %1 -f \"%2\" clean").arg(parallelParam,
|
||||
extractRelativePath(
|
||||
mProject->directory(),
|
||||
mProject->makeFileName()));
|
||||
mArguments = cleanArgs;
|
||||
} else if (mRebuild) {
|
||||
mArguments = QString(" -f \"%1\" clean").arg(extractRelativePath(
|
||||
mProject->directory(),
|
||||
mProject->makeFileName()));
|
||||
mExtraCompilersList.append(mCompiler);
|
||||
mExtraOutputFilesList.append("");
|
||||
mExtraArgumentsList.append(QString(" %1 -f \"%2\" all").arg(parallelParam,
|
||||
extractRelativePath(
|
||||
mProject->directory(),
|
||||
mProject->makeFileName())));
|
||||
mArguments = cleanArgs;
|
||||
mExtraCompilersList << mCompiler;
|
||||
mExtraOutputFilesList << "";
|
||||
mExtraArgumentsList << makeAllArgs;
|
||||
} else {
|
||||
mArguments = QString(" %1 -f \"%2\" all").arg(parallelParam,
|
||||
extractRelativePath(
|
||||
mProject->directory(),
|
||||
mProject->makeFileName()));
|
||||
mArguments = makeAllArgs;
|
||||
}
|
||||
mDirectory = mProject->directory();
|
||||
|
||||
log(tr("Processing makefile:"));
|
||||
log("--------");
|
||||
log(tr("- makefile processer: %1").arg(mCompiler));
|
||||
log(tr("- Command: %1 %2").arg(extractFileName(mCompiler)).arg(mArguments));
|
||||
QString command = escapeCommandForLog(mCompiler, mArguments);
|
||||
log(tr("- Command: %1").arg(command));
|
||||
log("");
|
||||
|
||||
return true;
|
||||
|
|
|
@ -43,7 +43,7 @@ bool SDCCFileCompiler::prepareForCompile()
|
|||
mArguments += getCCompileArguments(false);
|
||||
mArguments += getCIncludeArguments();
|
||||
mArguments += getProjectIncludeArguments();
|
||||
mArguments = QString("--syntax-only \"%1\"").arg(mFilename);
|
||||
mArguments += {"--syntax-only", mFilename};
|
||||
mDirectory = extractFileDir(mFilename);
|
||||
return true;
|
||||
}
|
||||
|
@ -73,16 +73,15 @@ bool SDCCFileCompiler::prepareForCompile()
|
|||
mNoStartup = (val==COMPILER_OPTION_ON);
|
||||
if (mNoStartup) {
|
||||
mRelFilename = changeFileExt(mFilename,SDCC_REL_SUFFIX);
|
||||
mArguments += QString(" -c \"%1\"").arg(mFilename);
|
||||
mExtraCompilersList.append(mCompiler);
|
||||
QString args = getLibraryArguments(FileType::CSource);
|
||||
args += QString(" -o \"%1\" \"%2\" ").arg(mIhxFilename, mRelFilename);
|
||||
mExtraArgumentsList.append(args);
|
||||
mExtraOutputFilesList.append("");
|
||||
mArguments += {"-c", mFilename};
|
||||
mExtraCompilersList << mCompiler;
|
||||
QStringList args = getLibraryArguments(FileType::CSource);
|
||||
args += {"-o", mIhxFilename, mRelFilename};
|
||||
mExtraArgumentsList << args;
|
||||
mExtraOutputFilesList << "";
|
||||
} else {
|
||||
mArguments += getLibraryArguments(FileType::CSource);
|
||||
mArguments += QString(" \"%1\"").arg(mFilename);
|
||||
mArguments+=QString(" -o \"%1\"").arg(mIhxFilename);
|
||||
mArguments += {mFilename, "-o", mIhxFilename};
|
||||
}
|
||||
|
||||
if (compilerSet()->executableSuffix() == SDCC_HEX_SUFFIX) {
|
||||
|
@ -92,28 +91,26 @@ bool SDCCFileCompiler::prepareForCompile()
|
|||
return false;
|
||||
}
|
||||
mExtraCompilersList.append(packihx);
|
||||
QString args;
|
||||
args = QString(" \"%1\"").arg(mIhxFilename);
|
||||
mExtraArgumentsList.append(args);
|
||||
mExtraOutputFilesList.append(mOutputFile);
|
||||
QStringList args{mIhxFilename};
|
||||
mExtraArgumentsList << args;
|
||||
mExtraOutputFilesList << mOutputFile;
|
||||
} else if (compilerSet()->executableSuffix() == SDCC_BIN_SUFFIX) {
|
||||
QString makebin = compilerSet()->findProgramInBinDirs(MAKEBIN_PROGRAM);
|
||||
if (makebin.isEmpty()) {
|
||||
error(tr("Can't find \"%1\".\n").arg(PACKIHX_PROGRAM));
|
||||
return false;
|
||||
}
|
||||
mExtraCompilersList.push_back(makebin);
|
||||
QString args;
|
||||
args = QString(" \"%1\"").arg(mIhxFilename);
|
||||
args+=QString(" \"%1\"").arg(mOutputFile);
|
||||
mExtraArgumentsList.push_back(args);
|
||||
mExtraOutputFilesList.append("");
|
||||
mExtraCompilersList << makebin;
|
||||
QStringList args{mIhxFilename, mOutputFile};
|
||||
mExtraArgumentsList << args;
|
||||
mExtraOutputFilesList << "";
|
||||
}
|
||||
|
||||
log(tr("Processing %1 source file:").arg(strFileType));
|
||||
log("------------------");
|
||||
log(tr("- %1 Compiler: %2").arg(strFileType).arg(mCompiler));
|
||||
log(tr("- Command: %1 %2").arg(extractFileName(mCompiler)).arg(mArguments));
|
||||
QString command = escapeCommandForLog(mCompiler, mArguments);
|
||||
log(tr("- Command: %1").arg(command));
|
||||
mDirectory = extractFileDir(mFilename);
|
||||
mStartCompileTime = QDateTime::currentDateTime();
|
||||
return true;
|
||||
|
|
|
@ -18,6 +18,9 @@
|
|||
#include "../project.h"
|
||||
#include "compilermanager.h"
|
||||
#include "../systemconsts.h"
|
||||
#include "qt_utils/utils.h"
|
||||
#include "utils.h"
|
||||
#include "utils/escape.h"
|
||||
|
||||
#include <QDir>
|
||||
|
||||
|
@ -40,17 +43,17 @@ void SDCCProjectCompiler::createStandardMakeFile()
|
|||
newMakeFile(file);
|
||||
QString suffix = compilerSet()->executableSuffix();
|
||||
if (suffix == SDCC_IHX_SUFFIX) {
|
||||
writeln(file,"$(BIN): $(OBJ)");
|
||||
writeln(file,"\t$(CC) $(LIBS) -o $(BIN) $(LINKOBJ)");
|
||||
writeln(file,"$(BIN_TAR): $(OBJ)");
|
||||
writeln(file,"\t$(CC) $(LIBS) -o $(BIN_ARG) $(LINKOBJ)");
|
||||
} else {
|
||||
writeln(file,"$(IHX): $(OBJ)\n");
|
||||
writeln(file,"\t$(CC) $(LIBS) -o $(IHX) $(LINKOBJ)");
|
||||
writeln(file,"$(IHX_TAR): $(OBJ)\n");
|
||||
writeln(file,"\t$(CC) $(LIBS) -o $(IHX_ARG) $(LINKOBJ)");
|
||||
if (suffix == SDCC_HEX_SUFFIX) {
|
||||
writeln(file,"$(BIN): $(IHX)");
|
||||
writeln(file,"\t$(PACKIHX) $(IHX) > $(BIN)");
|
||||
writeln(file,"$(BIN_TAR): $(IHX_DEP)");
|
||||
writeln(file,"\t$(PACKIHX) $(IHX_ARG) > $(BIN_ARG)");
|
||||
} else {
|
||||
writeln(file,"$(BIN): $(IHX)");
|
||||
writeln(file,"\t$(MAKEBIN) $(IHX) $(BIN)");
|
||||
writeln(file,"$(BIN_TAR): $(IHX_DEP)");
|
||||
writeln(file,"\t$(MAKEBIN) $(IHX_ARG) $(BIN_ARG)");
|
||||
}
|
||||
}
|
||||
writeMakeObjFilesRules(file);
|
||||
|
@ -102,9 +105,9 @@ void SDCCProjectCompiler::writeMakeHeader(QFile &file)
|
|||
void SDCCProjectCompiler::writeMakeDefines(QFile &file)
|
||||
{
|
||||
// Get list of object files
|
||||
QString Objects;
|
||||
QString LinkObjects;
|
||||
QString cleanObjects;
|
||||
QStringList Objects;
|
||||
QStringList LinkObjects;
|
||||
QStringList cleanObjects;
|
||||
|
||||
// Create a list of object files
|
||||
foreach(const PProjectUnit &unit, mProject->unitList()) {
|
||||
|
@ -122,67 +125,52 @@ void SDCCProjectCompiler::writeMakeDefines(QFile &file)
|
|||
QString fullObjFile = includeTrailingPathDelimiter(mProject->options().objectOutput)
|
||||
+ extractFileName(unit->fileName());
|
||||
QString relativeObjFile = extractRelativePath(mProject->directory(), changeFileExt(fullObjFile, SDCC_REL_SUFFIX));
|
||||
QString objFile = genMakePath2(relativeObjFile);
|
||||
Objects += ' ' + objFile;
|
||||
#ifdef Q_OS_WIN
|
||||
cleanObjects += ' ' + genMakePath1(relativeObjFile).replace("/",QDir::separator());
|
||||
#else
|
||||
cleanObjects += ' ' + genMakePath1(relativeObjFile);
|
||||
#endif
|
||||
Objects << relativeObjFile;
|
||||
cleanObjects << localizePath(relativeObjFile);
|
||||
if (unit->link()) {
|
||||
LinkObjects += ' ' + genMakePath1(relativeObjFile);
|
||||
LinkObjects << relativeObjFile;
|
||||
}
|
||||
} else {
|
||||
Objects += ' ' + genMakePath2(changeFileExt(RelativeName, SDCC_REL_SUFFIX));
|
||||
#ifdef Q_OS_WIN
|
||||
cleanObjects += ' ' + genMakePath1(changeFileExt(RelativeName, SDCC_REL_SUFFIX)).replace("/",QDir::separator());
|
||||
#else
|
||||
cleanObjects += ' ' + genMakePath1(changeFileExt(RelativeName, SDCC_REL_SUFFIX));
|
||||
#endif
|
||||
Objects << changeFileExt(RelativeName, SDCC_REL_SUFFIX);
|
||||
cleanObjects << localizePath(changeFileExt(RelativeName, SDCC_REL_SUFFIX));
|
||||
if (unit->link())
|
||||
LinkObjects = LinkObjects + ' ' + genMakePath1(changeFileExt(RelativeName, SDCC_REL_SUFFIX));
|
||||
LinkObjects << changeFileExt(RelativeName, SDCC_REL_SUFFIX);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Objects = Objects.trimmed();
|
||||
LinkObjects = LinkObjects.trimmed();
|
||||
QString cc = extractFileName(compilerSet()->CCompiler());
|
||||
|
||||
QStringList cCompileArguments = getCCompileArguments(mOnlyCheckSyntax);
|
||||
if (cCompileArguments.contains("-g3"))
|
||||
cCompileArguments << "-D__DEBUG__";
|
||||
QStringList libraryArguments = getLibraryArguments(FileType::Project);
|
||||
QStringList cIncludeArguments = getCIncludeArguments() + getProjectIncludeArguments();
|
||||
|
||||
// Get list of applicable flags
|
||||
QString cCompileArguments = getCCompileArguments(mOnlyCheckSyntax);
|
||||
QString libraryArguments = getLibraryArguments(FileType::Project);
|
||||
QString cIncludeArguments = getCIncludeArguments() + " " + getProjectIncludeArguments();
|
||||
QString executable = extractRelativePath(mProject->makeFileName(), mProject->executable());
|
||||
QString cleanExe = localizePath(executable);
|
||||
QString ihx = extractRelativePath(mProject->makeFileName(), changeFileExt(mProject->executable(), SDCC_IHX_SUFFIX));
|
||||
QString cleanIhx = localizePath(ihx);
|
||||
|
||||
if (cCompileArguments.indexOf(" -g3")>=0
|
||||
|| cCompileArguments.startsWith("-g3")) {
|
||||
cCompileArguments += " -D__DEBUG__";
|
||||
}
|
||||
writeln(file, "CC = " + escapeArgumentForMakefileVariableValue(cc, true));
|
||||
writeln(file, "PACKIHX = " PACKIHX_PROGRAM);
|
||||
writeln(file, "MAKEBIN = " MAKEBIN_PROGRAM);
|
||||
|
||||
writeln(file,"CC = " + extractFileName(compilerSet()->CCompiler()));
|
||||
writeln(file,QString("PACKIHX = ") + PACKIHX_PROGRAM);
|
||||
writeln(file,QString("MAKEBIN = ") + MAKEBIN_PROGRAM);
|
||||
|
||||
writeln(file,"OBJ = " + Objects);
|
||||
writeln(file,"LINKOBJ = " + LinkObjects);
|
||||
#ifdef Q_OS_WIN
|
||||
writeln(file,"CLEANOBJ = " + cleanObjects +
|
||||
+ " " + genMakePath1(extractRelativePath(mProject->makeFileName(), changeFileExt(mProject->executable(),SDCC_IHX_SUFFIX))).replace("/",QDir::separator())
|
||||
+ " " + genMakePath1(extractRelativePath(mProject->makeFileName(), mProject->executable())).replace("/",QDir::separator()) );
|
||||
#else
|
||||
writeln(file,"CLEANOBJ = " + cleanObjects +
|
||||
+ " " + genMakePath1(extractRelativePath(mProject->makeFileName(), mProject->executable())));
|
||||
#endif
|
||||
libraryArguments.replace('\\', '/');
|
||||
writeln(file,"LIBS = " + libraryArguments);
|
||||
cIncludeArguments.replace('\\', '/');
|
||||
writeln(file,"INCS = " + cIncludeArguments);
|
||||
writeln(file,"IHX = " + genMakePath1(extractRelativePath(mProject->makeFileName(), changeFileExt(mProject->executable(), SDCC_IHX_SUFFIX))));
|
||||
writeln(file,"BIN = " + genMakePath1(extractRelativePath(mProject->makeFileName(), mProject->executable())));
|
||||
//writeln(file,"ENCODINGS = -finput-charset=utf-8 -fexec-charset='+GetSystemCharsetName);
|
||||
cCompileArguments.replace('\\', '/');
|
||||
writeln(file,"CFLAGS = $(INCS) " + cCompileArguments);
|
||||
writeln(file, QString("RM = ") + CLEAN_PROGRAM );
|
||||
writeln(file, "OBJ = " + escapeFilenamesForMakefilePrerequisite(Objects));
|
||||
writeln(file, "LINKOBJ = " + escapeArgumentsForMakefileVariableValue(LinkObjects));
|
||||
writeln(file,"CLEANOBJ = " + escapeArgumentsForMakefileVariableValue(cleanObjects) + ' ' +
|
||||
escapeArgumentForMakefileVariableValue(cleanIhx, false) + ' ' +
|
||||
escapeArgumentForMakefileVariableValue(cleanExe, false));
|
||||
writeln(file, "LIBS = " + escapeArgumentsForMakefileVariableValue(libraryArguments));
|
||||
writeln(file, "INCS = " + escapeArgumentsForMakefileVariableValue(cIncludeArguments));
|
||||
writeln(file, "IHX_TAR = " + escapeFilenameForMakefileTarget(ihx));
|
||||
writeln(file, "IHX_DEP = " + escapeFilenameForMakefilePrerequisite(ihx));
|
||||
writeln(file, "IHX_ARG = " + escapeArgumentForMakefileVariableValue(ihx, false));
|
||||
writeln(file, "BIN_TAR = " + escapeFilenameForMakefileTarget(executable));
|
||||
writeln(file, "BIN_DEP = " + escapeFilenameForMakefilePrerequisite(executable));
|
||||
writeln(file, "BIN_ARG = " + escapeArgumentForMakefileVariableValue(executable, false));
|
||||
writeln(file, "CFLAGS = $(INCS) " + escapeArgumentsForMakefileVariableValue(cCompileArguments));
|
||||
writeln(file, "RM = " CLEAN_PROGRAM);
|
||||
|
||||
writeln(file);
|
||||
}
|
||||
|
@ -191,7 +179,7 @@ void SDCCProjectCompiler::writeMakeTarget(QFile &file)
|
|||
{
|
||||
writeln(file, ".PHONY: all all-before all-after clean clean-custom");
|
||||
writeln(file);
|
||||
writeln(file, "all: all-before $(BIN) all-after");
|
||||
writeln(file, "all: all-before $(BIN_DEP) all-after");
|
||||
writeln(file);
|
||||
|
||||
}
|
||||
|
@ -199,7 +187,7 @@ void SDCCProjectCompiler::writeMakeTarget(QFile &file)
|
|||
void SDCCProjectCompiler::writeMakeIncludes(QFile &file)
|
||||
{
|
||||
foreach(const QString& s, mProject->options().makeIncludes) {
|
||||
writeln(file, "include " + genMakePath1(s));
|
||||
writeln(file, "include " + escapeFilenameForMakefileInclude(s));
|
||||
}
|
||||
if (!mProject->options().makeIncludes.isEmpty()) {
|
||||
writeln(file);
|
||||
|
@ -209,16 +197,13 @@ void SDCCProjectCompiler::writeMakeIncludes(QFile &file)
|
|||
void SDCCProjectCompiler::writeMakeClean(QFile &file)
|
||||
{
|
||||
writeln(file, "clean: clean-custom");
|
||||
QString target="$(CLEANOBJ)";
|
||||
|
||||
writeln(file, QString("\t-$(RM) %1 > %2 2>&1").arg(target,NULL_FILE));
|
||||
writeln(file, QString("\t-$(RM) $(CLEANOBJ) > %1 2>&1").arg(NULL_FILE));
|
||||
writeln(file);
|
||||
}
|
||||
|
||||
void SDCCProjectCompiler::writeMakeObjFilesRules(QFile &file)
|
||||
{
|
||||
PCppParser parser = mProject->cppParser();
|
||||
QString precompileStr;
|
||||
|
||||
QList<PProjectUnit> projectUnits=mProject->unitList();
|
||||
foreach(const PProjectUnit &unit, projectUnits) {
|
||||
|
@ -233,7 +218,7 @@ void SDCCProjectCompiler::writeMakeObjFilesRules(QFile &file)
|
|||
QString shortFileName = extractRelativePath(mProject->makeFileName(),unit->fileName());
|
||||
|
||||
writeln(file);
|
||||
QString objStr=genMakePath2(shortFileName);
|
||||
QString objStr = escapeFilenameForMakefilePrerequisite(shortFileName);
|
||||
// if we have scanned it, use scanned info
|
||||
if (parser && parser->fileScanned(unit->fileName())) {
|
||||
QSet<QString> fileIncludes = parser->getIncludedFiles(unit->fileName());
|
||||
|
@ -241,32 +226,34 @@ void SDCCProjectCompiler::writeMakeObjFilesRules(QFile &file)
|
|||
if (unit2==unit)
|
||||
continue;
|
||||
if (fileIncludes.contains(unit2->fileName())) {
|
||||
objStr = objStr + ' ' + genMakePath2(extractRelativePath(mProject->makeFileName(),unit2->fileName()));
|
||||
QString header = extractRelativePath(mProject->makeFileName(),unit2->fileName());
|
||||
objStr = objStr + ' ' + escapeFilenameForMakefilePrerequisite(header);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
foreach(const PProjectUnit &unit2, projectUnits) {
|
||||
FileType fileType = getFileType(unit2->fileName());
|
||||
if (fileType == FileType::CHeader || fileType==FileType::CppHeader)
|
||||
objStr = objStr + ' ' + genMakePath2(extractRelativePath(mProject->makeFileName(),unit2->fileName()));
|
||||
if (fileType == FileType::CHeader || fileType==FileType::CppHeader) {
|
||||
QString header = extractRelativePath(mProject->makeFileName(),unit2->fileName());
|
||||
objStr = objStr + ' ' + escapeFilenameForMakefilePrerequisite(header);
|
||||
}
|
||||
}
|
||||
QString objFileName;
|
||||
QString objFileName2;
|
||||
}
|
||||
QString objFileNameTarget;
|
||||
QString objFileNameCommand;
|
||||
if (!mProject->options().objectOutput.isEmpty()) {
|
||||
QString fullObjname = includeTrailingPathDelimiter(mProject->options().objectOutput) +
|
||||
extractFileName(unit->fileName());
|
||||
objFileName = genMakePath2(extractRelativePath(mProject->makeFileName(), changeFileExt(fullObjname, SDCC_REL_SUFFIX)));
|
||||
objFileName2 = genMakePath1(extractRelativePath(mProject->makeFileName(), changeFileExt(fullObjname, SDCC_REL_SUFFIX)));
|
||||
// if (!extractFileDir(ObjFileName).isEmpty()) {
|
||||
// objStr = genMakePath2(includeTrailingPathDelimiter(extractFileDir(ObjFileName))) + objStr;
|
||||
// }
|
||||
QString objFile = extractRelativePath(mProject->makeFileName(), changeFileExt(fullObjname, SDCC_REL_SUFFIX));
|
||||
objFileNameTarget = escapeFilenameForMakefileTarget(objFile);
|
||||
objFileNameCommand = escapeArgumentForMakefileRecipe(objFile, false);
|
||||
} else {
|
||||
objFileName = genMakePath2(changeFileExt(shortFileName, SDCC_REL_SUFFIX));
|
||||
objFileName2 = genMakePath1(changeFileExt(shortFileName, SDCC_REL_SUFFIX));
|
||||
QString objFile = changeFileExt(shortFileName, SDCC_REL_SUFFIX);
|
||||
objFileNameTarget = escapeFilenameForMakefileTarget(objFile);
|
||||
objFileNameCommand = escapeArgumentForMakefileRecipe(objFile, false);
|
||||
}
|
||||
|
||||
objStr = objFileName + ": "+objStr+precompileStr;
|
||||
objStr = objFileNameTarget + ": " + objStr;
|
||||
|
||||
writeln(file, objStr);
|
||||
|
||||
|
@ -278,7 +265,7 @@ void SDCCProjectCompiler::writeMakeObjFilesRules(QFile &file)
|
|||
// Or roll our own
|
||||
} else {
|
||||
if (fileType==FileType::CSource) {
|
||||
writeln(file, "\t$(CC) $(CFLAGS) -c " + genMakePath1(shortFileName));
|
||||
writeln(file, "\t$(CC) $(CFLAGS) -c " + escapeArgumentForMakefileRecipe(shortFileName, false));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -327,35 +314,40 @@ bool SDCCProjectCompiler::prepareForCompile()
|
|||
} else {
|
||||
parallelParam = QString("-j%1").arg(mProject->options().parellelBuildingJobs);
|
||||
}
|
||||
} else {
|
||||
parallelParam = "-j1";
|
||||
}
|
||||
|
||||
QString makefile =
|
||||
extractRelativePath(mProject->directory(), mProject->makeFileName());
|
||||
QStringList cleanArgs{
|
||||
"-f",
|
||||
makefile,
|
||||
"clean",
|
||||
};
|
||||
QStringList makeAllArgs{
|
||||
parallelParam,
|
||||
"-f",
|
||||
makefile,
|
||||
"all",
|
||||
};
|
||||
if (onlyClean()) {
|
||||
mArguments = QString(" %1 -f \"%2\" clean").arg(parallelParam,
|
||||
extractRelativePath(
|
||||
mProject->directory(),
|
||||
mProject->makeFileName()));
|
||||
mArguments = cleanArgs;
|
||||
} else if (mRebuild) {
|
||||
mArguments = QString(" -f \"%1\" clean").arg(extractRelativePath(
|
||||
mProject->directory(),
|
||||
mProject->makeFileName()));
|
||||
mExtraCompilersList.append(mCompiler);
|
||||
mExtraOutputFilesList.append("");
|
||||
mExtraArgumentsList.append(QString(" %1 -f \"%2\" all").arg(parallelParam,
|
||||
extractRelativePath(
|
||||
mProject->directory(),
|
||||
mProject->makeFileName())));
|
||||
mArguments = cleanArgs;
|
||||
mExtraCompilersList << mCompiler;
|
||||
mExtraOutputFilesList << "";
|
||||
mExtraArgumentsList << makeAllArgs;
|
||||
} else {
|
||||
mArguments = QString(" %1 -f \"%2\" all").arg(parallelParam,
|
||||
extractRelativePath(
|
||||
mProject->directory(),
|
||||
mProject->makeFileName()));
|
||||
mArguments = makeAllArgs;
|
||||
}
|
||||
mDirectory = mProject->directory();
|
||||
|
||||
log(tr("Processing makefile:"));
|
||||
log("--------");
|
||||
log(tr("- makefile processer: %1").arg(mCompiler));
|
||||
log(tr("- Command: %1 %2").arg(extractFileName(mCompiler)).arg(mArguments));
|
||||
QString command = escapeCommandForLog(mCompiler, mArguments);
|
||||
log(tr("- Command: %1").arg(command));
|
||||
log("");
|
||||
|
||||
return true;
|
||||
|
|
|
@ -46,7 +46,7 @@ bool StdinCompiler::prepareForCompile()
|
|||
}
|
||||
switch(fileType) {
|
||||
case FileType::CSource:
|
||||
mArguments += " -x c - ";
|
||||
mArguments += {"-x", "c", "-"};
|
||||
mArguments += getCCompileArguments(mOnlyCheckSyntax);
|
||||
mArguments += getCIncludeArguments();
|
||||
mArguments += getProjectIncludeArguments();
|
||||
|
@ -54,7 +54,7 @@ bool StdinCompiler::prepareForCompile()
|
|||
mCompiler = compilerSet()->CCompiler();
|
||||
break;
|
||||
case FileType::GAS:
|
||||
mArguments += " -x assembler - ";
|
||||
mArguments += {"-x", "assembler", "-"};
|
||||
mArguments += getCCompileArguments(mOnlyCheckSyntax);
|
||||
mArguments += getCIncludeArguments();
|
||||
mArguments += getProjectIncludeArguments();
|
||||
|
@ -64,7 +64,7 @@ bool StdinCompiler::prepareForCompile()
|
|||
case FileType::CppSource:
|
||||
case FileType::CppHeader:
|
||||
case FileType::CHeader:
|
||||
mArguments += " -x c++ - ";
|
||||
mArguments += {"-x", "c++", "-"};
|
||||
mArguments += getCppCompileArguments(mOnlyCheckSyntax);
|
||||
mArguments += getCppIncludeArguments();
|
||||
mArguments += getProjectIncludeArguments();
|
||||
|
@ -87,7 +87,8 @@ bool StdinCompiler::prepareForCompile()
|
|||
log(tr("Processing %1 source file:").arg(strFileType));
|
||||
log("------------------");
|
||||
log(tr("%1 Compiler: %2").arg(strFileType).arg(mCompiler));
|
||||
log(tr("Command: %1 %2").arg(extractFileName(mCompiler)).arg(mArguments));
|
||||
QString command = escapeCommandForLog(mCompiler, mArguments);
|
||||
log(tr("Command: %1").arg(command));
|
||||
mDirectory = extractFileDir(mFilename);
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
*/
|
||||
#include "debugger.h"
|
||||
#include "utils.h"
|
||||
#include "utils/parsearg.h"
|
||||
#include "mainwindow.h"
|
||||
#include "editor.h"
|
||||
#include "settings.h"
|
||||
|
@ -140,7 +141,7 @@ bool Debugger::start(int compilerSetIndex, const QString& inferior, const QStrin
|
|||
//deleted when thread finished
|
||||
QStringList params;
|
||||
if (pSettings->executor().useParams())
|
||||
params = splitProcessCommand(pSettings->executor().params());
|
||||
params = parseArgumentsWithoutVariables(pSettings->executor().params());
|
||||
mTarget = new DebugTarget(inferior,compilerSet->debugServer(),pSettings->debugger().GDBServerPort(),params);
|
||||
if (pSettings->executor().redirectInput())
|
||||
mTarget->setInputFile(pSettings->executor().inputFilename());
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
#include <QDir>
|
||||
#include <QScreen>
|
||||
#include <QLockFile>
|
||||
#include <QFontDatabase>
|
||||
#include "common.h"
|
||||
#include "colorscheme.h"
|
||||
#include "iconsmanager.h"
|
||||
|
|
|
@ -22,6 +22,8 @@
|
|||
#include "settings.h"
|
||||
#include "qsynedit/constants.h"
|
||||
#include "debugger.h"
|
||||
#include "utils/escape.h"
|
||||
#include "utils/parsearg.h"
|
||||
#include "widgets/cpudialog.h"
|
||||
#include "widgets/filepropertiesdialog.h"
|
||||
#include "widgets/filenameeditdelegate.h"
|
||||
|
@ -3376,28 +3378,29 @@ void MainWindow::updateTools()
|
|||
QAction* action = new QAction(item->title,ui->menuTools);
|
||||
connect(action, &QAction::triggered,
|
||||
[item] (){
|
||||
QString program = parseMacros(item->program);
|
||||
QString workDir = parseMacros(item->workingDirectory);
|
||||
QString params = parseMacros(item->parameters);
|
||||
QMap<QString, QString> macros = devCppMacroVariables();
|
||||
QString program = parseMacros(item->program, macros);
|
||||
QString workDir = parseMacros(item->workingDirectory, macros);
|
||||
QStringList params = parseArguments(item->parameters, macros, true);
|
||||
if (!program.endsWith(".bat",Qt::CaseInsensitive)) {
|
||||
QTemporaryFile file(QDir::tempPath()+QDir::separator()+"XXXXXX.bat");
|
||||
file.setAutoRemove(false);
|
||||
if (file.open()) {
|
||||
file.write(QString("cd /d \"%1\"")
|
||||
.arg(localizePath(workDir))
|
||||
.toLocal8Bit()+LINE_BREAKER);
|
||||
file.write((program+" "+params).toLocal8Bit()
|
||||
file.write(escapeCommandForPlatformShell(
|
||||
"cd", {"/d", localizePath(workDir)}
|
||||
).toLocal8Bit() + LINE_BREAKER);
|
||||
file.write(escapeCommandForPlatformShell(program, params).toLocal8Bit()
|
||||
+ LINE_BREAKER);
|
||||
file.close();
|
||||
if (item->pauseAfterExit) {
|
||||
executeFile(
|
||||
includeTrailingPathDelimiter(pSettings->dirs().appLibexecDir())+CONSOLE_PAUSER,
|
||||
" 1 \""+localizePath(file.fileName())+"\" ",
|
||||
{"1", localizePath(file.fileName())},
|
||||
workDir, file.fileName());
|
||||
} else {
|
||||
executeFile(
|
||||
file.fileName(),
|
||||
"",
|
||||
{},
|
||||
workDir, file.fileName());
|
||||
}
|
||||
}
|
||||
|
@ -3405,7 +3408,7 @@ void MainWindow::updateTools()
|
|||
if (item->pauseAfterExit) {
|
||||
executeFile(
|
||||
includeTrailingPathDelimiter(pSettings->dirs().appLibexecDir())+CONSOLE_PAUSER,
|
||||
" 1 \""+program+"\" "+params,
|
||||
QStringList{"1", program} + params,
|
||||
workDir, "");
|
||||
} else {
|
||||
executeFile(
|
||||
|
|
|
@ -1423,11 +1423,7 @@ void Project::buildPrivateResource()
|
|||
if (
|
||||
(getFileType(unit->fileName()) == FileType::WindowsResourceSource)
|
||||
&& unit->compile() )
|
||||
contents.append("#include \"" +
|
||||
genMakePath(
|
||||
extractRelativePath(directory(), unit->fileName()),
|
||||
false,
|
||||
false) + "\"");
|
||||
contents.append("#include \"" + extractRelativePath(directory(), unit->fileName()) + "\"");
|
||||
}
|
||||
|
||||
if (!mOptions.icon.isEmpty()) {
|
||||
|
@ -1450,11 +1446,8 @@ void Project::buildPrivateResource()
|
|||
contents.append("//");
|
||||
if (!mOptions.exeOutput.isEmpty())
|
||||
contents.append(
|
||||
"1 24 \"" +
|
||||
genMakePath2(
|
||||
includeTrailingPathDelimiter(mOptions.exeOutput)
|
||||
+ extractFileName(executable()))
|
||||
+ ".Manifest\"");
|
||||
"1 24 \"" + includeTrailingPathDelimiter(mOptions.exeOutput)
|
||||
+ extractFileName(executable()) + ".Manifest\"");
|
||||
else
|
||||
contents.append("1 24 \"" + extractFileName(executable()) + ".Manifest\"");
|
||||
}
|
||||
|
|
|
@ -15,8 +15,8 @@
|
|||
{
|
||||
"name": "CoreTerminal",
|
||||
"path": "coreterminal",
|
||||
"argsPattern": "$term -e \"$command\"",
|
||||
"comment": "The pair of quotation mark around `$command` is only a visual symbol, not actually required."
|
||||
"argsPattern": "$term -e \"$unix_command\"",
|
||||
"comment": "The pair of quotation mark around `$unix_command` is only a visual symbol, not actually required."
|
||||
},
|
||||
{
|
||||
"name": "iTerm2",
|
||||
|
@ -26,7 +26,7 @@
|
|||
{
|
||||
"name": "kermit",
|
||||
"path": "kermit",
|
||||
"argsPattern": "$term -e \"$command\""
|
||||
"argsPattern": "$term -e \"$unix_command\""
|
||||
},
|
||||
{
|
||||
"name": "Kitty",
|
||||
|
@ -36,12 +36,12 @@
|
|||
{
|
||||
"name": "ROXTerm",
|
||||
"path": "roxterm",
|
||||
"argsPattern": "$term -e \"$command\""
|
||||
"argsPattern": "$term -e \"$unix_command\""
|
||||
},
|
||||
{
|
||||
"name": "sakura",
|
||||
"path": "sakura",
|
||||
"argsPattern": "$term -e \"$command\""
|
||||
"argsPattern": "$term -e \"$unix_command\""
|
||||
},
|
||||
{
|
||||
"name": "Termit",
|
||||
|
@ -51,7 +51,7 @@
|
|||
{
|
||||
"name": "Termite",
|
||||
"path": "termite",
|
||||
"argsPattern": "$term -e \"$command\""
|
||||
"argsPattern": "$term -e \"$unix_command\""
|
||||
},
|
||||
{
|
||||
"name": "Tilix",
|
||||
|
@ -116,7 +116,7 @@
|
|||
{
|
||||
"name": "Terminology (Enlightenment)",
|
||||
"path": "terminology",
|
||||
"argsPattern": "$term -e \"$command\""
|
||||
"argsPattern": "$term -e \"$unix_command\""
|
||||
},
|
||||
{
|
||||
"name": "Xfce Terminal",
|
||||
|
@ -131,7 +131,7 @@
|
|||
{
|
||||
"name": "Elementary Terminal",
|
||||
"path": "io.elementary.terminal",
|
||||
"argsPattern": "$term -e \"$command\"",
|
||||
"argsPattern": "$term -e \"$unix_command\"",
|
||||
"comment": "confirm to quit"
|
||||
},
|
||||
{
|
||||
|
|
|
@ -19,6 +19,8 @@
|
|||
#include <QTextCodec>
|
||||
#include <algorithm>
|
||||
#include "utils.h"
|
||||
#include "utils/escape.h"
|
||||
#include "utils/parsearg.h"
|
||||
#include <QDir>
|
||||
#include "systemconsts.h"
|
||||
#include <QDebug>
|
||||
|
@ -1934,14 +1936,14 @@ Settings::CompilerSet::CompilerSet(const QJsonObject &set) :
|
|||
mFullLoaded = false;
|
||||
}
|
||||
|
||||
QStringList escapedCompileParams;
|
||||
QStringList compileParams;
|
||||
for (const QJsonValue ¶m : set["customCompileParams"].toArray())
|
||||
escapedCompileParams.append(escapeArgument(param.toString(), false));
|
||||
mCustomCompileParams = escapedCompileParams.join(' ');
|
||||
QStringList escapedLinkParams;
|
||||
compileParams << param.toString();
|
||||
mCustomCompileParams = escapeArgumentsForInputField(compileParams);
|
||||
QStringList linkParams;
|
||||
for (const QJsonValue ¶m : set["customLinkParams"].toArray())
|
||||
escapedLinkParams.append(escapeArgument(param.toString(), false));
|
||||
mCustomLinkParams = escapedLinkParams.join(' ');
|
||||
linkParams << param.toString();
|
||||
mCustomLinkParams = escapeArgumentsForInputField(linkParams);
|
||||
|
||||
if (!mAutoAddCharsetParams)
|
||||
mExecCharset = "UTF-8";
|
||||
|
@ -2597,7 +2599,7 @@ QStringList Settings::CompilerSet::defines(bool isCpp) {
|
|||
#endif
|
||||
|
||||
if (mUseCustomCompileParams) {
|
||||
QStringList extraParams = splitProcessCommand(mCustomCompileParams);
|
||||
QStringList extraParams = parseArgumentsWithoutVariables(mCustomCompileParams);
|
||||
arguments.append(extraParams);
|
||||
}
|
||||
arguments.append(NULL_FILE);
|
||||
|
@ -4125,6 +4127,11 @@ void Settings::Environment::checkAndSetTerminal()
|
|||
QCoreApplication::translate("Settings","Can't find terminal program!"));
|
||||
}
|
||||
|
||||
QMap<QString, QString> Settings::Environment::terminalArgsPatternMagicVariables()
|
||||
{
|
||||
return mTerminalArgsPatternMagicVariables;
|
||||
}
|
||||
|
||||
QList<Settings::Environment::TerminalItem> Settings::Environment::loadTerminalList() const
|
||||
{
|
||||
#ifdef Q_OS_WINDOWS
|
||||
|
@ -4165,7 +4172,7 @@ bool Settings::Environment::isTerminalValid()
|
|||
// terminal patter is empty
|
||||
if (mTerminalArgumentsPattern.isEmpty()) return false;
|
||||
|
||||
QStringList patternItems = splitProcessCommand(mTerminalArgumentsPattern);
|
||||
QStringList patternItems = parseArguments(mTerminalArgumentsPattern, mTerminalArgsPatternMagicVariables, false);
|
||||
|
||||
if (!(patternItems.contains("$argv")
|
||||
|| patternItems.contains("$command")
|
||||
|
@ -4242,6 +4249,17 @@ void Settings::Environment::setTheme(const QString &theme)
|
|||
mTheme = theme;
|
||||
}
|
||||
|
||||
const QMap<QString, QString> Settings::Environment::mTerminalArgsPatternMagicVariables = {
|
||||
{"term", "$term"},
|
||||
{"integrated_term", "$integrated_term"},
|
||||
{"argv", "$argv"},
|
||||
{"command", "$command"},
|
||||
{"unix_command", "$unix_command"},
|
||||
{"dos_command", "$dos_command"},
|
||||
{"lpCommandLine", "$lpCommandLine"},
|
||||
{"tmpfile", "$tmpfile"},
|
||||
};
|
||||
|
||||
Settings::Executor::Executor(Settings *settings):_Base(settings, SETTING_EXECUTOR)
|
||||
{
|
||||
|
||||
|
@ -6708,21 +6726,39 @@ std::tuple<QString, QStringList, std::unique_ptr<QTemporaryFile>> wrapCommandFor
|
|||
wrappedArgs.append(includeTrailingPathDelimiter(pSettings->dirs().appDir())+terminal);
|
||||
else if (patternItem == "$argv")
|
||||
wrappedArgs.append(payloadArgsWithArgv0);
|
||||
else if (patternItem == "$command") {
|
||||
else if (patternItem == "$command" || patternItem == "$unix_command") {
|
||||
// “$command” is for compatibility; previously used on multiple Unix terms
|
||||
QStringList escapedArgs;
|
||||
for (int i = 0; i < payloadArgsWithArgv0.length(); i++) {
|
||||
auto &arg = payloadArgsWithArgv0[i];
|
||||
auto escaped = escapeArgument(arg, i == 0);
|
||||
auto escaped = escapeArgument(arg, i == 0, EscapeArgumentRule::BourneAgainShellPretty);
|
||||
escapedArgs.append(escaped);
|
||||
}
|
||||
wrappedArgs.push_back(escapedArgs.join(' '));
|
||||
} else if (patternItem == "$tmpfile") {
|
||||
} else if (patternItem == "$dos_command") {
|
||||
QStringList escapedArgs;
|
||||
for (int i = 0; i < payloadArgsWithArgv0.length(); i++) {
|
||||
auto &arg = payloadArgsWithArgv0[i];
|
||||
auto escaped = escapeArgument(arg, i == 0, EscapeArgumentRule::WindowsCommandPrompt);
|
||||
escapedArgs.append(escaped);
|
||||
}
|
||||
wrappedArgs.push_back(escapedArgs.join(' '));
|
||||
} else if (patternItem == "$lpCommandLine") {
|
||||
QStringList escapedArgs;
|
||||
for (int i = 0; i < payloadArgsWithArgv0.length(); i++) {
|
||||
auto &arg = payloadArgsWithArgv0[i];
|
||||
auto escaped = escapeArgument(arg, i == 0, EscapeArgumentRule::WindowsCreateProcess);
|
||||
escapedArgs.append(escaped);
|
||||
}
|
||||
wrappedArgs.push_back(escapedArgs.join(' '));
|
||||
} else if (patternItem == "$tmpfile" || patternItem == "$tmpfile.command") {
|
||||
// “$tmpfile” is for compatibility; previously used on macOS Terminal.app
|
||||
temproryFile = std::make_unique<QTemporaryFile>(QDir::tempPath() + "/redpanda_XXXXXX.command");
|
||||
if (temproryFile->open()) {
|
||||
QStringList escapedArgs;
|
||||
for (int i = 0; i < payloadArgsWithArgv0.length(); i++) {
|
||||
auto &arg = payloadArgsWithArgv0[i];
|
||||
auto escaped = escapeArgument(arg, i == 0);
|
||||
auto escaped = escapeArgument(arg, i == 0, EscapeArgumentRule::BourneAgainShellPretty);
|
||||
escapedArgs.append(escaped);
|
||||
}
|
||||
temproryFile->write(escapedArgs.join(' ').toUtf8());
|
||||
|
@ -6731,6 +6767,21 @@ std::tuple<QString, QStringList, std::unique_ptr<QTemporaryFile>> wrapCommandFor
|
|||
QFile(temproryFile->fileName()).setPermissions(QFileDevice::ReadOwner | QFileDevice::WriteOwner | QFileDevice::ExeOwner);
|
||||
}
|
||||
wrappedArgs.push_back(temproryFile->fileName());
|
||||
} else if (patternItem == "$tmpfile.bat") {
|
||||
temproryFile = std::make_unique<QTemporaryFile>(QDir::tempPath() + "/redpanda_XXXXXX.bat");
|
||||
if (temproryFile->open()) {
|
||||
QStringList escapedArgs;
|
||||
for (int i = 0; i < payloadArgsWithArgv0.length(); i++) {
|
||||
auto &arg = payloadArgsWithArgv0[i];
|
||||
auto escaped = escapeArgument(arg, i == 0, EscapeArgumentRule::WindowsCommandPrompt);
|
||||
escapedArgs.append(escaped);
|
||||
}
|
||||
temproryFile->write(escapedArgs.join(' ').toLocal8Bit());
|
||||
temproryFile->write("\r\n");
|
||||
temproryFile->flush();
|
||||
QFile(temproryFile->fileName()).setPermissions(QFileDevice::ReadOwner | QFileDevice::WriteOwner | QFileDevice::ExeOwner);
|
||||
}
|
||||
wrappedArgs.push_back(temproryFile->fileName());
|
||||
} else
|
||||
wrappedArgs.push_back(patternItem);
|
||||
}
|
||||
|
@ -6741,5 +6792,5 @@ std::tuple<QString, QStringList, std::unique_ptr<QTemporaryFile>> wrapCommandFor
|
|||
|
||||
std::tuple<QString, QStringList, std::unique_ptr<QTemporaryFile>> wrapCommandForTerminalEmulator(const QString &terminal, const QString &argsPattern, const QStringList &payloadArgsWithArgv0)
|
||||
{
|
||||
return wrapCommandForTerminalEmulator(terminal, splitProcessCommand(argsPattern), payloadArgsWithArgv0);
|
||||
return wrapCommandForTerminalEmulator(terminal, parseArguments(argsPattern, Settings::Environment::terminalArgsPatternMagicVariables(), false), payloadArgsWithArgv0);
|
||||
}
|
||||
|
|
|
@ -631,6 +631,8 @@ public:
|
|||
|
||||
QList<TerminalItem> loadTerminalList() const;
|
||||
|
||||
static QMap<QString, QString> terminalArgsPatternMagicVariables();
|
||||
|
||||
private:
|
||||
bool isTerminalValid();
|
||||
void checkAndSetTerminal();
|
||||
|
@ -653,6 +655,8 @@ public:
|
|||
bool mUseCustomTerminal;
|
||||
bool mHideNonSupportFilesInFileView;
|
||||
bool mOpenFilesInSingleInstance;
|
||||
|
||||
static const QMap<QString, QString> mTerminalArgsPatternMagicVariables;
|
||||
// _Base interface
|
||||
protected:
|
||||
void doSave() override;
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
#include "../iconsmanager.h"
|
||||
#include "../systemconsts.h"
|
||||
#include "../compiler/executablerunner.h"
|
||||
#include "utils/escape.h"
|
||||
|
||||
#include <QFileDialog>
|
||||
#include <QMessageBox>
|
||||
|
@ -43,18 +44,16 @@ EnvironmentProgramsWidget::~EnvironmentProgramsWidget()
|
|||
auto EnvironmentProgramsWidget::resolveExecArguments(const QString &terminalPath, const QString &argsPattern)
|
||||
-> std::tuple<QString, QStringList, std::unique_ptr<QTemporaryFile>>
|
||||
{
|
||||
QString shell = defaultShell();
|
||||
QStringList payloadArgs{shell, "-c", "echo hello; sleep 3"};
|
||||
return wrapCommandForTerminalEmulator(terminalPath, argsPattern, payloadArgs);
|
||||
return wrapCommandForTerminalEmulator(terminalPath, argsPattern, platformCommandForTerminalArgsPreview());
|
||||
}
|
||||
|
||||
void EnvironmentProgramsWidget::updateCommandPreview(const QString &terminalPath, const QString &argsPattern)
|
||||
{
|
||||
auto [filename, arguments, fileOwner] = resolveExecArguments(terminalPath, argsPattern);
|
||||
for (auto &arg : arguments)
|
||||
arg = escapeArgument(arg, false);
|
||||
arg = escapeArgument(arg, false, platformShellEscapeArgumentRule());
|
||||
|
||||
ui->labelCmdPreviewResult->setPlainText(escapeArgument(filename, true) + " " + arguments.join(' '));
|
||||
ui->labelCmdPreviewResult->setPlainText(escapeArgument(filename, true, platformShellEscapeArgumentRule()) + " " + arguments.join(' '));
|
||||
}
|
||||
|
||||
void EnvironmentProgramsWidget::autoDetectAndUpdateArgumentsPattern(const QString &terminalPath)
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
#include "../settings.h"
|
||||
#include "../iconsmanager.h"
|
||||
#include "../systemconsts.h"
|
||||
#include "utils/parsearg.h"
|
||||
|
||||
#include <QFileDialog>
|
||||
#include <QJsonDocument>
|
||||
|
@ -90,7 +91,7 @@ void ExecutorGeneralWidget::updateIcons(const QSize &/*size*/)
|
|||
|
||||
void ExecutorGeneralWidget::on_txtExecuteParamaters_textChanged(const QString &commandLine)
|
||||
{
|
||||
QStringList parsed = splitProcessCommand(commandLine);
|
||||
QStringList parsed = parseArgumentsWithoutVariables(commandLine);
|
||||
QJsonArray obj = QJsonArray::fromStringList(parsed);
|
||||
ui->txtParsedArgsInJson->setText(QJsonDocument{obj}.toJson());
|
||||
}
|
||||
|
|
|
@ -19,6 +19,8 @@
|
|||
#include "../mainwindow.h"
|
||||
#include "../project.h"
|
||||
#include "../iconsmanager.h"
|
||||
#include "utils.h"
|
||||
#include "utils/escape.h"
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#include <sysinfoapi.h>
|
||||
|
@ -83,7 +85,7 @@ void ProjectCompileParamatersWidget::on_btnChooseLib_clicked()
|
|||
);
|
||||
if (!files.isEmpty()) {
|
||||
foreach (const QString& file,files) {
|
||||
ui->txtLinker->appendPlainText(" "+genMakePath1(file));
|
||||
ui->txtLinker->appendPlainText(" " + escapeArgument(file, false, EscapeArgumentRule::BourneAgainShellPretty));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,9 @@
|
|||
#include "../mainwindow.h"
|
||||
#include "../settings.h"
|
||||
#include "../iconsmanager.h"
|
||||
#include "utils.h"
|
||||
#include "utils/escape.h"
|
||||
#include "utils/parsearg.h"
|
||||
|
||||
#include <QFileDialog>
|
||||
#include <QMessageBox>
|
||||
|
@ -132,9 +135,11 @@ void ToolsGeneralWidget::onEdited()
|
|||
|
||||
void ToolsGeneralWidget::updateDemo()
|
||||
{
|
||||
ui->txtDemo->setText(
|
||||
parseMacros(ui->txtProgram->text())+ " " +
|
||||
parseMacros(ui->txtParameters->text()));
|
||||
QMap<QString,QString> macros = devCppMacroVariables();
|
||||
ui->txtDemo->setText(escapeCommandForPlatformShell(
|
||||
parseMacros(ui->txtProgram->text(), macros),
|
||||
parseArguments(ui->txtParameters->text(), macros, true)
|
||||
));
|
||||
}
|
||||
|
||||
ToolsModel::ToolsModel(QObject *parent):QAbstractListModel(parent)
|
||||
|
|
|
@ -0,0 +1,157 @@
|
|||
#include <cstdlib>
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QDebug>
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QProcess>
|
||||
#include <QString>
|
||||
|
||||
#include "utils/escape.h"
|
||||
|
||||
int testIndex = 0;
|
||||
|
||||
QByteArray content = "main(){}";
|
||||
|
||||
void testMake(QString name)
|
||||
{
|
||||
++testIndex;
|
||||
auto dir = QString("test-escape-%1").arg(testIndex);
|
||||
auto srcName = name + ".c";
|
||||
auto objName = name + ".o";
|
||||
#ifdef Q_OS_WIN
|
||||
auto binName = name + ".exe";
|
||||
#else
|
||||
auto binName = name;
|
||||
#endif
|
||||
auto includeMfName = name + ".mf";
|
||||
|
||||
auto fail = [&name](const QString& msg) {
|
||||
qDebug() << "Error in test" << testIndex << name << ":" << msg;
|
||||
exit(1);
|
||||
};
|
||||
|
||||
// create directory
|
||||
{
|
||||
auto cwd = QDir();
|
||||
if (cwd.exists(dir)) {
|
||||
if (!QDir(dir).removeRecursively())
|
||||
fail("cannot remove directory");
|
||||
}
|
||||
if (!cwd.mkdir(dir))
|
||||
fail("cannot create directory");
|
||||
}
|
||||
|
||||
// create source file
|
||||
{
|
||||
auto srcPath = QString("%1/%2").arg(dir, srcName);
|
||||
QFile f(srcPath);
|
||||
if (!f.open(QIODevice::WriteOnly))
|
||||
fail("cannot create source file");
|
||||
if (f.write(content) != content.size())
|
||||
fail("cannot write to source file");
|
||||
f.close();
|
||||
}
|
||||
|
||||
// create included makefile
|
||||
{
|
||||
auto file = QString("%1/%2").arg(dir, includeMfName);
|
||||
QFile f(file);
|
||||
if (!f.open(QIODevice::WriteOnly))
|
||||
fail("cannot create included makefile");
|
||||
f.close();
|
||||
}
|
||||
|
||||
// create makefile
|
||||
{
|
||||
QStringList mf;
|
||||
mf << "BIN_DEP = " + escapeFilenameForMakefilePrerequisite(binName);
|
||||
mf << "BIN_TAR = " + escapeFilenameForMakefileTarget(binName);
|
||||
mf << "BIN_ARG = " + escapeArgumentForMakefileVariableValue(binName, false);
|
||||
mf << "OBJS_DEP = " + escapeFilenameForMakefilePrerequisite(objName);
|
||||
mf << "OBJS_ARG = " + escapeArgumentForMakefileVariableValue(objName, false);
|
||||
mf << "include " + escapeFilenameForMakefileInclude(includeMfName);
|
||||
mf << ".PHONY: all clean";
|
||||
mf << "all: $(BIN_DEP)";
|
||||
mf << "$(BIN_TAR): $(OBJS_DEP)";
|
||||
mf << "\tgcc -o $(BIN_ARG) $(OBJS_ARG)";
|
||||
mf << escapeFilenameForMakefileTarget(objName) + ": " + escapeFilenameForMakefilePrerequisite(srcName);
|
||||
mf << "\tgcc -o " + escapeArgumentForMakefileRecipe(objName, false) + " -c " + escapeArgumentForMakefileRecipe(srcName, false);
|
||||
mf << "clean:";
|
||||
mf << "\trm -f $(BIN_ARG) $(OBJS_ARG)";
|
||||
|
||||
auto file = QString("%1/makefile").arg(dir);
|
||||
QFile f(file);
|
||||
if (!f.open(QIODevice::WriteOnly))
|
||||
fail("cannot create makefile");
|
||||
f.write(mf.join("\n").toUtf8());
|
||||
}
|
||||
|
||||
// run make
|
||||
{
|
||||
QProcess p;
|
||||
p.setWorkingDirectory(dir);
|
||||
#ifdef Q_OS_WIN
|
||||
p.setProgram("mingw32-make.exe");
|
||||
#else
|
||||
p.setProgram("make");
|
||||
#endif
|
||||
p.start();
|
||||
p.waitForFinished();
|
||||
if (p.exitCode() != 0) {
|
||||
qDebug() << "Error in test" << testIndex << name << ": make failed";
|
||||
exit(1);
|
||||
}
|
||||
auto binFile = QString("%1/%2").arg(dir, binName);
|
||||
if (!QFile(binFile).exists()) {
|
||||
qDebug() << "Error in test" << testIndex << name << ": executable not properly created";
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
testMake("simple");
|
||||
testMake("dollar$dollar");
|
||||
testMake("paren(paren");
|
||||
testMake("paren)paren");
|
||||
testMake("pair(of)paren");
|
||||
testMake("bracket[bracket");
|
||||
testMake("bracket]bracket");
|
||||
testMake("pair[of]brackets");
|
||||
testMake("brace{brace");
|
||||
testMake("brace}brace");
|
||||
testMake("pair{of}braces");
|
||||
testMake("hash#hash");
|
||||
testMake("percent%percent");
|
||||
testMake("ampersand&ersand");
|
||||
testMake("space space");
|
||||
testMake("quote'quote");
|
||||
testMake("complex$(complex)complex");
|
||||
testMake("complex${complex}complex");
|
||||
|
||||
#ifndef Q_OS_WIN
|
||||
testMake("colon:colon");
|
||||
testMake("less<less");
|
||||
testMake("greater>greater");
|
||||
testMake("pair<of>angle");
|
||||
testMake("asterisk*asterisk");
|
||||
testMake("question?question");
|
||||
testMake("escape\033escape");
|
||||
testMake(R"(quote"quote)");
|
||||
testMake(R"(backslash\backslash)");
|
||||
testMake(R"(complex\#complex)");
|
||||
testMake(R"(complex\ complex)");
|
||||
testMake(R"(weird\)");
|
||||
testMake(R"(weird\\)");
|
||||
testMake(R"(weird\\\)");
|
||||
testMake(R"(weird\\\\)");
|
||||
#endif
|
||||
|
||||
// seems impossible:
|
||||
// testMake("tab\ttab");
|
||||
// testMake("newline\nnewline");
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -4,6 +4,8 @@
|
|||
#include <QDateTime>
|
||||
#include <QApplication>
|
||||
#include <QDesktopServices>
|
||||
#include <QSysInfo>
|
||||
#include <QVersionNumber>
|
||||
#include "editor.h"
|
||||
#include "editorlist.h"
|
||||
#include "settings.h"
|
||||
|
@ -23,72 +25,6 @@ using pIsWow64Process2_t = BOOL (WINAPI *)(
|
|||
);
|
||||
#endif
|
||||
|
||||
QStringList splitProcessCommand(const QString &cmd)
|
||||
{
|
||||
QStringList result;
|
||||
SplitProcessCommandQuoteType quoteType = SplitProcessCommandQuoteType::None;
|
||||
int i=0;
|
||||
QString current;
|
||||
while (i<cmd.length()) {
|
||||
switch (cmd[i].unicode()) {
|
||||
case ' ':
|
||||
case '\t':
|
||||
case '\r':
|
||||
case '\n':
|
||||
if (quoteType == SplitProcessCommandQuoteType::None) {
|
||||
if (!current.isEmpty()) {
|
||||
result.append(current);
|
||||
}
|
||||
current = "";
|
||||
} else {
|
||||
current += cmd[i];
|
||||
}
|
||||
i++;
|
||||
break;
|
||||
case '\"':
|
||||
switch(quoteType) {
|
||||
case SplitProcessCommandQuoteType::None:
|
||||
quoteType = SplitProcessCommandQuoteType::Double;
|
||||
break;
|
||||
case SplitProcessCommandQuoteType::Double:
|
||||
quoteType = SplitProcessCommandQuoteType::None;
|
||||
break;
|
||||
default:
|
||||
current+=cmd[i];
|
||||
}
|
||||
i++;
|
||||
break;
|
||||
case '\'':
|
||||
switch(quoteType) {
|
||||
case SplitProcessCommandQuoteType::None:
|
||||
quoteType = SplitProcessCommandQuoteType::Single;
|
||||
break;
|
||||
case SplitProcessCommandQuoteType::Single:
|
||||
quoteType = SplitProcessCommandQuoteType::None;
|
||||
break;
|
||||
default:
|
||||
current+=cmd[i];
|
||||
}
|
||||
i++;
|
||||
break;
|
||||
case '\\':
|
||||
current += cmd[i];
|
||||
i++;
|
||||
if (i<cmd.length()) {
|
||||
current += cmd[i];
|
||||
i++;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
current += cmd[i];
|
||||
i++;
|
||||
}
|
||||
}
|
||||
if (!current.isEmpty())
|
||||
result.append(current);
|
||||
return result;
|
||||
}
|
||||
|
||||
FileType getFileType(const QString &filename)
|
||||
{
|
||||
if (filename.endsWith(".s",PATH_SENSITIVITY)) {
|
||||
|
@ -176,31 +112,6 @@ FileType getFileType(const QString &filename)
|
|||
return FileType::Other;
|
||||
}
|
||||
|
||||
QString genMakePath(const QString &fileName, bool escapeSpaces, bool encloseInQuotes)
|
||||
{
|
||||
QString result = fileName;
|
||||
|
||||
// Convert backslashes to slashes
|
||||
result.replace('\\','/');
|
||||
if (escapeSpaces) {
|
||||
result.replace(' ',"\\ ");
|
||||
}
|
||||
if (encloseInQuotes)
|
||||
if (result.contains(' '))
|
||||
result = '"'+result+'"';
|
||||
return result;
|
||||
}
|
||||
|
||||
QString genMakePath1(const QString &fileName)
|
||||
{
|
||||
return genMakePath(fileName, false, true);
|
||||
}
|
||||
|
||||
QString genMakePath2(const QString &fileName)
|
||||
{
|
||||
return genMakePath(fileName, true, false);
|
||||
}
|
||||
|
||||
bool programHasConsole(const QString & filename)
|
||||
{
|
||||
#ifdef Q_OS_WIN
|
||||
|
@ -229,33 +140,47 @@ bool programHasConsole(const QString & filename)
|
|||
}
|
||||
|
||||
QString parseMacros(const QString &s)
|
||||
{
|
||||
return parseMacros(s, devCppMacroVariables());
|
||||
}
|
||||
|
||||
QString parseMacros(const QString &s, const QMap<QString, QString> ¯os)
|
||||
{
|
||||
QString result = s;
|
||||
for (auto it = macros.begin(); it != macros.end(); ++it) {
|
||||
QString key = it.key();
|
||||
QString value = it.value();
|
||||
result.replace('<' + key + '>', value);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
QMap<QString, QString> devCppMacroVariables()
|
||||
{
|
||||
Editor *e = pMainWindow->editorList()->getEditor();
|
||||
|
||||
result.replace("<DEFAULT>", localizePath(QDir::currentPath()));
|
||||
result.replace("<DEVCPP>", localizePath(pSettings->dirs().executable()));
|
||||
result.replace("<DEVCPPVERSION>", REDPANDA_CPP_VERSION);
|
||||
result.replace("<EXECPATH>", localizePath(pSettings->dirs().appDir()));
|
||||
QDate today = QDate::currentDate();
|
||||
QDateTime now = QDateTime::currentDateTime();
|
||||
|
||||
result.replace("<DATE>", today.toString("yyyy-MM-dd"));
|
||||
result.replace("<DATETIME>", now.toString("yyyy-MM-dd hh:mm:ss"));
|
||||
QMap<QString, QString> result = {
|
||||
{"DEFAULT", localizePath(QDir::currentPath())},
|
||||
{"DEVCPP", localizePath(pSettings->dirs().executable())},
|
||||
{"DEVCPPVERSION", REDPANDA_CPP_VERSION},
|
||||
{"EXECPATH", localizePath(pSettings->dirs().appDir())},
|
||||
{"DATE", QDate::currentDate().toString("yyyy-MM-dd")},
|
||||
{"DATETIME", QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss")}
|
||||
};
|
||||
|
||||
Settings::PCompilerSet compilerSet = pSettings->compilerSets().defaultSet();
|
||||
if (compilerSet) {
|
||||
// Only provide the first cpp include dir
|
||||
if (compilerSet->defaultCppIncludeDirs().count() > 0)
|
||||
result.replace("<INCLUDE>", localizePath(compilerSet->defaultCppIncludeDirs().front()));
|
||||
result["INCLUDE"] = localizePath(compilerSet->defaultCppIncludeDirs().front());
|
||||
else
|
||||
result.replace("<INCLUDE>","");
|
||||
result["INCLUDE"] = "";
|
||||
|
||||
// Only provide the first lib dir
|
||||
if (compilerSet->defaultLibDirs().count() > 0)
|
||||
result.replace("<LIB>", localizePath(compilerSet->defaultLibDirs().front()));
|
||||
result["LIB"] = localizePath(compilerSet->defaultLibDirs().front());
|
||||
else
|
||||
result.replace("<LIB>","");
|
||||
result["LIB"] = "";
|
||||
}
|
||||
|
||||
if (e != nullptr && !e->inProject()) { // Non-project editor macros
|
||||
|
@ -266,40 +191,41 @@ QString parseMacros(const QString &s)
|
|||
} else {
|
||||
exeSuffix = DEFAULT_EXECUTABLE_SUFFIX;
|
||||
}
|
||||
result.replace("<EXENAME>", extractFileName(changeFileExt(e->filename(), exeSuffix)));
|
||||
result.replace("<EXEFILE>", localizePath(changeFileExt(e->filename(), exeSuffix)));
|
||||
result.replace("<PROJECTNAME>", extractFileName(e->filename()));
|
||||
result.replace("<PROJECTFILE>", localizePath(e->filename()));
|
||||
result.replace("<PROJECTFILENAME>", extractFileName(e->filename()));
|
||||
result.replace("<PROJECTPATH>", localizePath(extractFileDir(e->filename())));
|
||||
result["EXENAME"] = extractFileName(changeFileExt(e->filename(), exeSuffix));
|
||||
result["EXEFILE"] = localizePath(changeFileExt(e->filename(), exeSuffix));
|
||||
result["PROJECTNAME"] = extractFileName(e->filename());
|
||||
result["PROJECTFILE"] = localizePath(e->filename());
|
||||
result["PROJECTFILENAME"] = extractFileName(e->filename());
|
||||
result["PROJECTPATH"] = localizePath(extractFileDir(e->filename()));
|
||||
} else if (pMainWindow->project()) {
|
||||
result.replace("<EXENAME>", extractFileName(pMainWindow->project()->executable()));
|
||||
result.replace("<EXEFILE>", localizePath(pMainWindow->project()->executable()));
|
||||
result.replace("<PROJECTNAME>", pMainWindow->project()->name());
|
||||
result.replace("<PROJECTFILE>", localizePath(pMainWindow->project()->filename()));
|
||||
result.replace("<PROJECTFILENAME>", extractFileName(pMainWindow->project()->filename()));
|
||||
result.replace("<PROJECTPATH>", localizePath(pMainWindow->project()->directory()));
|
||||
result["EXENAME"] = extractFileName(pMainWindow->project()->executable());
|
||||
result["EXEFILE"] = localizePath(pMainWindow->project()->executable());
|
||||
result["PROJECTNAME"] = pMainWindow->project()->name();
|
||||
result["PROJECTFILE"] = localizePath(pMainWindow->project()->filename());
|
||||
result["PROJECTFILENAME"] = extractFileName(pMainWindow->project()->filename());
|
||||
result["PROJECTPATH"] = localizePath(pMainWindow->project()->directory());
|
||||
} else {
|
||||
result.replace("<EXENAME>", "");
|
||||
result.replace("<EXEFILE>", "");
|
||||
result.replace("<PROJECTNAME>", "");
|
||||
result.replace("<PROJECTFILE>", "");
|
||||
result.replace("<PROJECTFILENAME>", "");
|
||||
result.replace("<PROJECTPATH>", "");
|
||||
result["EXENAME"] = "";
|
||||
result["EXEFILE"] = "";
|
||||
result["PROJECTNAME"] = "";
|
||||
result["PROJECTFILE"] = "";
|
||||
result["PROJECTFILENAME"] = "";
|
||||
result["PROJECTPATH"] = "";
|
||||
}
|
||||
|
||||
// Editor macros
|
||||
if (e != nullptr) {
|
||||
result.replace("<SOURCENAME>", extractFileName(e->filename()));
|
||||
result.replace("<SOURCEFILE>", localizePath(e->filename()));
|
||||
result.replace("<SOURCEPATH>", localizePath(extractFileDir(e->filename())));
|
||||
result.replace("<WORDXY>", e->wordAtCursor());
|
||||
result["SOURCENAME"] = extractFileName(e->filename());
|
||||
result["SOURCEFILE"] = localizePath(e->filename());
|
||||
result["SOURCEPATH"] = localizePath(extractFileDir(e->filename()));
|
||||
result["WORDXY"] = e->wordAtCursor();
|
||||
} else {
|
||||
result.replace("<SOURCENAME>", "");
|
||||
result.replace("<SOURCEFILE>", "");
|
||||
result.replace("<SOURCEPATH>", "");
|
||||
result.replace("<WORDXY>", "");
|
||||
result["SOURCENAME"] = "";
|
||||
result["SOURCEFILE"] = "";
|
||||
result["SOURCEPATH"] = "";
|
||||
result["WORDXY"] = "";
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -447,11 +373,11 @@ QByteArray runAndGetOutput(const QString &cmd, const QString& workingDir, const
|
|||
return result;
|
||||
}
|
||||
|
||||
void executeFile(const QString &fileName, const QString ¶ms, const QString &workingDir, const QString &tempFile)
|
||||
void executeFile(const QString &fileName, const QStringList ¶ms, const QString &workingDir, const QString &tempFile)
|
||||
{
|
||||
ExecutableRunner* runner=new ExecutableRunner(
|
||||
fileName,
|
||||
splitProcessCommand(params),
|
||||
params,
|
||||
workingDir);
|
||||
runner->connect(runner, &QThread::finished,
|
||||
[runner,tempFile](){
|
||||
|
@ -630,125 +556,16 @@ QStringList getExecutableSearchPaths()
|
|||
#endif
|
||||
}
|
||||
|
||||
QString escapeArgument(const QString &arg, [[maybe_unused]] bool isFirstArg)
|
||||
{
|
||||
auto argContainsOneOf = [&arg](auto... ch) { return (arg.contains(ch) || ...); };
|
||||
|
||||
#ifdef Q_OS_WINDOWS
|
||||
// See https://stackoverflow.com/questions/31838469/how-do-i-convert-argv-to-lpcommandline-parameter-of-createprocess ,
|
||||
// and https://learn.microsoft.com/en-gb/archive/blogs/twistylittlepassagesallalike/everyone-quotes-command-line-arguments-the-wrong-way .
|
||||
|
||||
// TODO: investigate whether we need escaping for cmd.
|
||||
|
||||
if (!arg.isEmpty() && !argContainsOneOf(' ', '\t', '\n', '\v', '"'))
|
||||
return arg;
|
||||
|
||||
QString result = "\"";
|
||||
for (auto it = arg.begin(); ; ++it) {
|
||||
int nBackslash = 0;
|
||||
while (it != arg.end() && *it == '\\') {
|
||||
++it;
|
||||
++nBackslash;
|
||||
}
|
||||
if (it == arg.end()) {
|
||||
// Escape all backslashes, but let the terminating double quotation mark we add below be interpreted as a metacharacter.
|
||||
result.append(QString('\\').repeated(nBackslash * 2));
|
||||
break;
|
||||
} else if (*it == '"') {
|
||||
// Escape all backslashes and the following double quotation mark.
|
||||
result.append(QString('\\').repeated(nBackslash * 2 + 1));
|
||||
result.push_back(*it);
|
||||
} else {
|
||||
// Backslashes aren't special here.
|
||||
result.append(QString('\\').repeated(nBackslash));
|
||||
result.push_back(*it);
|
||||
}
|
||||
}
|
||||
result.push_back('"');
|
||||
return result;
|
||||
#else
|
||||
/* be speculative, but keep readability.
|
||||
* ref. https://pubs.opengroup.org/onlinepubs/9699919799.2018edition/utilities/V3_chap02.html
|
||||
*/
|
||||
if (arg.isEmpty())
|
||||
return R"("")";
|
||||
|
||||
/* POSIX say the following reserved words (may) have special meaning:
|
||||
* !, {, }, case, do, done, elif, else, esac, fi, for, if, in, then, until, while,
|
||||
* [[, ]], function, select,
|
||||
* only if used as the _first word_ of a command (or somewhere we dot not care).
|
||||
*/
|
||||
const static QSet<QString> reservedWord{
|
||||
"!", "{", "}", "case", "do", "done", "elif", "else", "esac", "fi", "for", "if", "in", "then", "until", "while",
|
||||
"[[", "]]", "function", "select",
|
||||
};
|
||||
if (isFirstArg && reservedWord.contains(arg))
|
||||
return QString(R"("%1")").arg(arg);
|
||||
|
||||
/* POSIX say “shall quote”:
|
||||
* '|', '&', ';', '<', '>', '(', ')', '$', '`', '\\', '"', '\'', ' ', '\t', '\n';
|
||||
* and “may need to be quoted”:
|
||||
* '*', '?', '[', '#', '~', '=', '%'.
|
||||
* among which “may need to be quoted” there are 4 kinds:
|
||||
* - wildcards '*', '?', '[' are “danger anywhere” (handle it as if “shall quote”);
|
||||
* - comment '#', home '~', is “danger at first char in any word”;
|
||||
* - (environment) variable '=' is “danger at any char in first word”;
|
||||
* - foreground '%' is “danger at first char in first word”.
|
||||
* although not mentioned by POSIX, bash’s brace expansion '{', '}' are also “danger anywhere”.
|
||||
*/
|
||||
bool isDoubleQuotingDanger = argContainsOneOf('$', '`', '\\', '"');
|
||||
bool isSingleQuotingDanger = arg.contains('\'');
|
||||
bool isDangerAnyChar = isDoubleQuotingDanger || isSingleQuotingDanger || argContainsOneOf(
|
||||
'|', '&', ';', '<', '>', '(', ')', ' ', '\t', '\n',
|
||||
'*', '?', '[',
|
||||
'{', '}'
|
||||
);
|
||||
bool isDangerFirstChar = (arg[0] == '#') || (arg[0] == '~');
|
||||
if (isFirstArg) {
|
||||
isDangerAnyChar = isDangerAnyChar || arg.contains('=');
|
||||
isDangerFirstChar = isDangerFirstChar || (arg[0] == '%');
|
||||
}
|
||||
|
||||
// a “safe” string
|
||||
if (!isDangerAnyChar && !isDangerFirstChar)
|
||||
return arg;
|
||||
|
||||
// prefer more-common double quoting
|
||||
if (!isDoubleQuotingDanger)
|
||||
return QString(R"("%1")").arg(arg);
|
||||
|
||||
// and then check the opportunity of single quoting
|
||||
if (!isSingleQuotingDanger)
|
||||
return QString("'%1'").arg(arg);
|
||||
|
||||
// escaping is necessary
|
||||
// use double quoting since it’s less tricky
|
||||
QString result = "\"";
|
||||
for (auto ch : arg) {
|
||||
if (ch == '$' || ch == '`' || ch == '\\' || ch == '"')
|
||||
result.push_back('\\');
|
||||
result.push_back(ch);
|
||||
}
|
||||
result.push_back('"');
|
||||
return result;
|
||||
|
||||
/* single quoting, which is roughly raw string, is possible and quite simple in programming:
|
||||
* 1. replace each single quote with `'\''`, which contains
|
||||
* - a single quote to close quoting,
|
||||
* - an escaped single quote representing the single quote itself, and
|
||||
* - a single quote to open quoting again;
|
||||
* 2. enclose the string with a pair of single quotes.
|
||||
* e.g. `o'clock` => `'o'\''clock'`, really tricky and hard to read.
|
||||
*/
|
||||
#endif
|
||||
}
|
||||
|
||||
QString defaultShell()
|
||||
QStringList platformCommandForTerminalArgsPreview()
|
||||
{
|
||||
#ifdef Q_OS_WINDOWS
|
||||
return "powershell.exe";
|
||||
QVersionNumber currentVersion = QVersionNumber::fromString(QSysInfo::kernelVersion());
|
||||
if (currentVersion >= QVersionNumber(6, 1))
|
||||
return {"powershell.exe", "-c", "echo hello; sleep 3"};
|
||||
else
|
||||
return {"cmd.exe", "/c", "echo hello & ping 127.0.0.1"};
|
||||
#else
|
||||
return "sh";
|
||||
return {"sh", "-c", "echo hello; sleep 3"};
|
||||
#endif
|
||||
}
|
||||
|
||||
|
|
|
@ -116,14 +116,12 @@ enum class ProblemCaseValidateType {
|
|||
};
|
||||
|
||||
FileType getFileType(const QString& filename);
|
||||
QStringList splitProcessCommand(const QString& cmd);
|
||||
|
||||
QString genMakePath(const QString& fileName,bool escapeSpaces, bool encloseInQuotes);
|
||||
QString genMakePath1(const QString& fileName);
|
||||
QString genMakePath2(const QString& fileName);
|
||||
bool programHasConsole(const QString& filename);
|
||||
|
||||
QString parseMacros(const QString& s);
|
||||
QString parseMacros(const QString& s, const QMap<QString, QString>& variables);
|
||||
QMap<QString, QString> devCppMacroVariables();
|
||||
|
||||
class CppParser;
|
||||
void resetCppParser(std::shared_ptr<CppParser> parser, int compilerSetIndex=-1);
|
||||
|
@ -138,7 +136,7 @@ QByteArray runAndGetOutput(const QString& cmd, const QString& workingDir, const
|
|||
void openFileFolderInExplorer(const QString& path);
|
||||
|
||||
void executeFile(const QString& fileName,
|
||||
const QString& params,
|
||||
const QStringList& params,
|
||||
const QString& workingDir,
|
||||
const QString& tempFile);
|
||||
|
||||
|
@ -169,9 +167,7 @@ QColor alphaBlend(const QColor &lower, const QColor &upper);
|
|||
|
||||
QStringList getExecutableSearchPaths();
|
||||
|
||||
QString escapeArgument(const QString &arg, bool isFirstArg);
|
||||
|
||||
QString defaultShell();
|
||||
QStringList platformCommandForTerminalArgsPreview();
|
||||
|
||||
QString appArch();
|
||||
QString osArch();
|
||||
|
|
|
@ -0,0 +1,305 @@
|
|||
/*
|
||||
* Copyright (C) 2020-2022 Roy Qu (royqh1979@gmail.com)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "utils/escape.h"
|
||||
|
||||
#include <QSet>
|
||||
#include <QRegularExpression>
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#define __builtin_unreachable() (__assume(0))
|
||||
#endif
|
||||
|
||||
static QString contextualBackslashEscaping(const QString &arg, const QSet<QChar> &needsEscaping, bool escapeFinal = true)
|
||||
{
|
||||
QString result;
|
||||
for (auto it = arg.begin(); ; ++it) {
|
||||
int nBackSlash = 0;
|
||||
while (it != arg.end() && *it == '\\') {
|
||||
++it;
|
||||
++nBackSlash;
|
||||
}
|
||||
if (it == arg.end()) {
|
||||
if (escapeFinal) {
|
||||
// escape all backslashes, but leave following character unescaped
|
||||
// (terminating double quote for CreateProcess, or LF or space in makefile)
|
||||
result.append(QString('\\').repeated(nBackSlash * 2));
|
||||
} else {
|
||||
// leave all backslashes unescaped, and add a space to protect LF
|
||||
result.append(QString('\\').repeated(nBackSlash));
|
||||
if (nBackSlash > 0)
|
||||
result.push_back(' ');
|
||||
}
|
||||
break;
|
||||
} else if (needsEscaping.contains(*it)) {
|
||||
// escape all backslashes and the following character
|
||||
result.append(QString('\\').repeated(nBackSlash * 2 + 1));
|
||||
result.push_back(*it);
|
||||
} else {
|
||||
// backslashes aren't special here
|
||||
result.append(QString('\\').repeated(nBackSlash));
|
||||
result.push_back(*it);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
QString escapeArgumentImplBourneAgainShellPretty(const QString &arg, bool isFirstArg)
|
||||
{
|
||||
// ref. https://pubs.opengroup.org/onlinepubs/9699919799.2018edition/utilities/V3_chap02.html
|
||||
|
||||
if (arg.isEmpty())
|
||||
return R"("")";
|
||||
|
||||
/* POSIX say the following reserved words (may) have special meaning:
|
||||
* !, {, }, case, do, done, elif, else, esac, fi, for, if, in, then, until, while,
|
||||
* [[, ]], function, select,
|
||||
* only if used as the _first word_ of a command (or somewhere we dot not care).
|
||||
*/
|
||||
const static QSet<QString> reservedWord{
|
||||
"!", "{", "}", "case", "do", "done", "elif", "else", "esac", "fi", "for", "if", "in", "then", "until", "while",
|
||||
"[[", "]]", "function", "select",
|
||||
};
|
||||
if (isFirstArg && reservedWord.contains(arg))
|
||||
return QString(R"("%1")").arg(arg);
|
||||
|
||||
/* POSIX say “shall quote”:
|
||||
* '|', '&', ';', '<', '>', '(', ')', '$', '`', '\\', '"', '\'', ' ', '\t', '\n';
|
||||
* and “may need to be quoted”:
|
||||
* '*', '?', '[', '#', '~', '=', '%'.
|
||||
* among which “may need to be quoted” there are 4 kinds:
|
||||
* - wildcards '*', '?', '[' are “danger anywhere” (handle it as if “shall quote”);
|
||||
* - comment '#', home '~', is “danger at first char in any word”;
|
||||
* - (environment) variable '=' is “danger at any char in first word”;
|
||||
* - foreground '%' is “danger at first char in first word”.
|
||||
* although not mentioned by POSIX, bash’s brace expansion '{', '}' are also “danger anywhere”.
|
||||
*/
|
||||
|
||||
static QRegularExpression doubleQuotingDangerChars(R"([`$\\"])");
|
||||
static QRegularExpression otherDangerChars(R"([|&;<>() \t\n*?\[\{\}])");
|
||||
bool isDoubleQuotingDanger = arg.contains(doubleQuotingDangerChars);
|
||||
bool isSingleQuotingDanger = arg.contains('\'');
|
||||
bool isDangerAnyChar = isDoubleQuotingDanger || isSingleQuotingDanger || arg.contains(otherDangerChars);
|
||||
bool isDangerFirstChar = (arg[0] == '#') || (arg[0] == '~');
|
||||
if (isFirstArg) {
|
||||
isDangerAnyChar = isDangerAnyChar || arg.contains('=');
|
||||
isDangerFirstChar = isDangerFirstChar || (arg[0] == '%');
|
||||
}
|
||||
|
||||
// a “safe” string
|
||||
if (!isDangerAnyChar && !isDangerFirstChar)
|
||||
return arg;
|
||||
|
||||
// prefer more-common double quoting
|
||||
if (!isDoubleQuotingDanger)
|
||||
return QString(R"("%1")").arg(arg);
|
||||
|
||||
// and then check the opportunity of single quoting
|
||||
if (!isSingleQuotingDanger)
|
||||
return QString("'%1'").arg(arg);
|
||||
|
||||
// escaping is necessary
|
||||
// use double quoting since it’s less tricky
|
||||
QString result = "\"";
|
||||
for (auto ch : arg) {
|
||||
if (ch == '$' || ch == '`' || ch == '\\' || ch == '"')
|
||||
result.push_back('\\');
|
||||
result.push_back(ch);
|
||||
}
|
||||
result.push_back('"');
|
||||
return result;
|
||||
}
|
||||
|
||||
QString escapeArgumentImplBourneAgainShellFast(QString arg)
|
||||
{
|
||||
/* 1. replace each single quote with `'\''`, which contains
|
||||
* - a single quote to close quoting,
|
||||
* - an escaped single quote representing the single quote itself, and
|
||||
* - a single quote to open quoting again. */
|
||||
arg.replace('\'', R"('\'')");
|
||||
/* 2. enclose the string with a pair of single quotes. */
|
||||
return '\'' + arg + '\'';
|
||||
}
|
||||
|
||||
QString escapeArgumentImplWindowsCreateProcess(const QString &arg, bool forceQuote)
|
||||
{
|
||||
// See https://stackoverflow.com/questions/31838469/how-do-i-convert-argv-to-lpcommandline-parameter-of-createprocess ,
|
||||
// and https://learn.microsoft.com/en-gb/archive/blogs/twistylittlepassagesallalike/everyone-quotes-command-line-arguments-the-wrong-way .
|
||||
|
||||
static QRegularExpression needQuoting(R"([ \t\n\v"])");
|
||||
if (!arg.isEmpty() && !forceQuote && !arg.contains(needQuoting))
|
||||
return arg;
|
||||
|
||||
return '"' + contextualBackslashEscaping(arg, {'"'}) + '"';
|
||||
}
|
||||
|
||||
QString escapeArgumentImplWindowsCommandPrompt(const QString &arg)
|
||||
{
|
||||
static QRegularExpression metaChars(R"([()%!^"<>&|])");
|
||||
bool containsMeta = arg.contains(metaChars);
|
||||
if (containsMeta) {
|
||||
QString quoted = escapeArgumentImplWindowsCreateProcess(arg, false);
|
||||
quoted.replace('^', "^^"); // handle itself first
|
||||
quoted.replace('(', "^(");
|
||||
quoted.replace(')', "^)");
|
||||
quoted.replace('%', "^%");
|
||||
quoted.replace('!', "^!");
|
||||
quoted.replace('"', "^\"");
|
||||
quoted.replace('<', "^<");
|
||||
quoted.replace('>', "^>");
|
||||
quoted.replace('&', "^&");
|
||||
quoted.replace('|', "^|");
|
||||
return quoted;
|
||||
} else
|
||||
return escapeArgumentImplWindowsCreateProcess(arg, false);
|
||||
}
|
||||
|
||||
QString escapeArgument(const QString &arg, bool isFirstArg, EscapeArgumentRule rule)
|
||||
{
|
||||
switch (rule) {
|
||||
case EscapeArgumentRule::BourneAgainShellPretty:
|
||||
return escapeArgumentImplBourneAgainShellPretty(arg, isFirstArg);
|
||||
case EscapeArgumentRule::BourneAgainShellFast:
|
||||
return escapeArgumentImplBourneAgainShellFast(arg);
|
||||
case EscapeArgumentRule::WindowsCreateProcess:
|
||||
return escapeArgumentImplWindowsCreateProcess(arg, false);
|
||||
case EscapeArgumentRule::WindowsCreateProcessForceQuote:
|
||||
return escapeArgumentImplWindowsCreateProcess(arg, true);
|
||||
case EscapeArgumentRule::WindowsCommandPrompt:
|
||||
return escapeArgumentImplWindowsCommandPrompt(arg);
|
||||
default:
|
||||
__builtin_unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
EscapeArgumentRule platformShellEscapeArgumentRule()
|
||||
{
|
||||
#ifdef Q_OS_WIN
|
||||
return EscapeArgumentRule::WindowsCommandPrompt;
|
||||
#else
|
||||
return EscapeArgumentRule::BourneAgainShellPretty;
|
||||
#endif
|
||||
}
|
||||
|
||||
QString escapeArgumentForPlatformShell(const QString &arg, bool isFirstArg)
|
||||
{
|
||||
return escapeArgument(arg, isFirstArg, platformShellEscapeArgumentRule());
|
||||
}
|
||||
|
||||
QString escapeCommandForPlatformShell(const QString &prog, const QStringList &args)
|
||||
{
|
||||
QStringList escapedArgs{escapeArgumentForPlatformShell(prog, true)};
|
||||
for (int i = 0; i < args.size(); ++i)
|
||||
escapedArgs << escapeArgumentForPlatformShell(args[i], false);
|
||||
return escapedArgs.join(' ');
|
||||
}
|
||||
|
||||
EscapeArgumentRule makefileRecipeEscapeArgumentRule()
|
||||
{
|
||||
#ifdef Q_OS_WIN
|
||||
/* Lord knows why.
|
||||
|
||||
standard CreateProcess or CMD escaping:
|
||||
child.exe -c main'.c -o main'.o
|
||||
yielding:
|
||||
0: [child.exe]
|
||||
1: [-c]
|
||||
2: [main.c -o main.o]
|
||||
that's not what we want.
|
||||
|
||||
however, if CMD escaping a malformed argument
|
||||
child.exe -c main'.c -o main'.o ^"mal \^" ^& calc^"
|
||||
yielding:
|
||||
0: [child.exe]
|
||||
1: [-c]
|
||||
2: [main'.c]
|
||||
3: [-o]
|
||||
4: [main'.o]
|
||||
5: [mal " & calc]
|
||||
it works?!?!?!
|
||||
|
||||
force-quoted CreateProcess escaping seems work on most cases.
|
||||
*/
|
||||
return EscapeArgumentRule::WindowsCreateProcessForceQuote;
|
||||
#else
|
||||
return EscapeArgumentRule::BourneAgainShellPretty;
|
||||
#endif
|
||||
}
|
||||
|
||||
QString escapeArgumentForMakefileVariableValue(const QString &arg, bool isFirstArg)
|
||||
{
|
||||
static QSet<QChar> needsMfEscaping = {'#'};
|
||||
QString recipeEscaped = escapeArgument(arg, isFirstArg, makefileRecipeEscapeArgumentRule());
|
||||
QString mfEscaped = contextualBackslashEscaping(recipeEscaped, needsMfEscaping);
|
||||
return mfEscaped.replace('$', "$$");
|
||||
}
|
||||
|
||||
QString escapeArgumentsForMakefileVariableValue(const QStringList &args)
|
||||
{
|
||||
QStringList escapedArgs;
|
||||
for (int i = 0; i < args.size(); ++i)
|
||||
escapedArgs << escapeArgumentForMakefileVariableValue(args[i], false);
|
||||
return escapedArgs.join(' ');
|
||||
}
|
||||
|
||||
QString escapeFilenameForMakefileInclude(const QString &filename)
|
||||
{
|
||||
static QSet<QChar> needsEscaping{'#', ' '};
|
||||
QString result = contextualBackslashEscaping(filename, needsEscaping);
|
||||
return result.replace('$', "$$");
|
||||
}
|
||||
|
||||
QString escapeFilenameForMakefileTarget(const QString &filename)
|
||||
{
|
||||
static QSet<QChar> needsEscaping{'#', ' ', ':', '%'};
|
||||
QString result = contextualBackslashEscaping(filename, needsEscaping);
|
||||
return result.replace('$', "$$");
|
||||
}
|
||||
|
||||
QString escapeFilenameForMakefilePrerequisite(const QString &filename)
|
||||
{
|
||||
static QSet<QChar> needsEscaping{'#', ' ', ':', '?', '*'};
|
||||
QString result = contextualBackslashEscaping(filename, needsEscaping, false);
|
||||
return result.replace('$', "$$");
|
||||
}
|
||||
|
||||
QString escapeFilenamesForMakefilePrerequisite(const QStringList &filenames)
|
||||
{
|
||||
QStringList escapedFilenames;
|
||||
for (int i = 0; i < filenames.size(); ++i)
|
||||
escapedFilenames << escapeFilenameForMakefilePrerequisite(filenames[i]);
|
||||
return escapedFilenames.join(' ');
|
||||
}
|
||||
|
||||
QString escapeArgumentForMakefileRecipe(const QString &arg, bool isFirstArg)
|
||||
{
|
||||
QString shellEscaped = escapeArgument(arg, isFirstArg, makefileRecipeEscapeArgumentRule());
|
||||
return shellEscaped.replace('$', "$$");
|
||||
}
|
||||
|
||||
QString escapeArgumentForInputField(const QString &arg, bool isFirstArg)
|
||||
{
|
||||
return escapeArgument(arg, isFirstArg, EscapeArgumentRule::BourneAgainShellPretty);
|
||||
}
|
||||
|
||||
QString escapeArgumentsForInputField(const QStringList &args)
|
||||
{
|
||||
QStringList escapedArgs;
|
||||
for (int i = 0; i < args.size(); ++i)
|
||||
escapedArgs << escapeArgumentForInputField(args[i], false);
|
||||
return escapedArgs.join(' ');
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* Copyright (C) 2020-2022 Roy Qu (royqh1979@gmail.com)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#ifndef ESCAPE_H
|
||||
#define ESCAPE_H
|
||||
|
||||
#include <QString>
|
||||
|
||||
enum class EscapeArgumentRule {
|
||||
BourneAgainShellPretty,
|
||||
BourneAgainShellFast,
|
||||
WindowsCreateProcess,
|
||||
WindowsCreateProcessForceQuote,
|
||||
WindowsCommandPrompt,
|
||||
};
|
||||
|
||||
QString escapeArgument(const QString &arg, bool isFirstArg, EscapeArgumentRule rule);
|
||||
|
||||
EscapeArgumentRule platformShellEscapeArgumentRule();
|
||||
QString escapeArgumentForPlatformShell(const QString &arg, bool isFirstArg);
|
||||
QString escapeCommandForPlatformShell(const QString &prog, const QStringList &args);
|
||||
|
||||
EscapeArgumentRule makefileRecipeEscapeArgumentRule();
|
||||
QString escapeArgumentForMakefileVariableValue(const QString &arg, bool isFirstArg);
|
||||
QString escapeArgumentsForMakefileVariableValue(const QStringList &args);
|
||||
QString escapeFilenameForMakefileInclude(const QString &filename);
|
||||
QString escapeFilenameForMakefileTarget(const QString &filename);
|
||||
QString escapeFilenameForMakefilePrerequisite(const QString &filename);
|
||||
QString escapeFilenamesForMakefilePrerequisite(const QStringList &filenames);
|
||||
QString escapeArgumentForMakefileRecipe(const QString &arg, bool isFirstArg);
|
||||
|
||||
QString escapeArgumentForInputField(const QString &arg, bool isFirstArg);
|
||||
QString escapeArgumentsForInputField(const QStringList &args);
|
||||
|
||||
#endif // ESCAPE_H
|
|
@ -0,0 +1,468 @@
|
|||
/*
|
||||
* Copyright (C) 2020-2022 Roy Qu (royqh1979@gmail.com)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "parsearg.h"
|
||||
|
||||
namespace ParseArgumentsDetail
|
||||
{
|
||||
|
||||
/*Before:
|
||||
'blahblah'
|
||||
^pos
|
||||
After:
|
||||
'blahblah'
|
||||
^pos */
|
||||
QString singleQuoted(const QString &command, int &pos)
|
||||
{
|
||||
QString result;
|
||||
while (pos < command.length() && command[pos] != '\'') {
|
||||
result.push_back(command[pos]);
|
||||
++pos;
|
||||
}
|
||||
if (pos < command.length())
|
||||
++pos; // eat closing quote
|
||||
return result;
|
||||
}
|
||||
|
||||
// read up to 3 octal digits
|
||||
QString readOctal(const QString &command, int &pos)
|
||||
{
|
||||
QString result;
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
if (pos < command.length() && command[pos] >= '0' && command[pos] <= '7') {
|
||||
result.push_back(command[pos]);
|
||||
++pos;
|
||||
} else
|
||||
break;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// read up to maxDigits hex digits
|
||||
QString readHex(const QString &command, int &pos, int maxDigits)
|
||||
{
|
||||
QString result;
|
||||
for (int i = 0; i < maxDigits; ++i) {
|
||||
if (pos < command.length() && (command[pos].isDigit() || (command[pos].toLower() >= 'a' && command[pos].toLower() <= 'f'))) {
|
||||
result.push_back(command[pos]);
|
||||
++pos;
|
||||
} else
|
||||
break;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/*Case 1: braced variable name (ingore POSIX operators, no nested braces)
|
||||
Before:
|
||||
${VARNAME.abc$}
|
||||
^pos
|
||||
After:
|
||||
${VARNAME.abc$}
|
||||
^pos
|
||||
Returns: value of `VARNAME.abc$`, "" if not found
|
||||
Case 2: command or arithmetic (no expansion, nested parentheses ok)
|
||||
Before:
|
||||
$(echo 1)
|
||||
^pos
|
||||
$((1+1))
|
||||
^pos
|
||||
After:
|
||||
$(echo 1)
|
||||
^pos
|
||||
$((1+1))
|
||||
^pos
|
||||
Returns: as is
|
||||
Case 3: ANSI-C quoting
|
||||
Before:
|
||||
$'blah\nblah\'blah'
|
||||
^pos
|
||||
After:
|
||||
$'blah\nblah\'blah'
|
||||
^pos
|
||||
Returns: unescaped string
|
||||
Case 4: normal variable name
|
||||
Before:
|
||||
$VAR_NAME-x
|
||||
^pos
|
||||
After:
|
||||
$VAR_NAME-x
|
||||
^pos
|
||||
Returns: value of `VAR_NAME`, "" if not found
|
||||
Case 5: all other invalid cases (though they may be valid in shell)
|
||||
Before:
|
||||
$123
|
||||
^pos
|
||||
After:
|
||||
$123
|
||||
^pos
|
||||
Returns: as is */
|
||||
QString variableExpansion(const QString &command, int &pos, const QMap<QString, QString> &variables, bool ansiCQuotingPermitted)
|
||||
{
|
||||
if (pos >= command.length())
|
||||
return "$";
|
||||
if (command[pos] == '{') {
|
||||
// case 1, read to closing brace
|
||||
QString varName;
|
||||
QString result;
|
||||
++pos; // eat opening brace
|
||||
while (pos < command.length() && command[pos] != '}') {
|
||||
varName.push_back(command[pos]);
|
||||
++pos;
|
||||
}
|
||||
if (pos < command.length()) {
|
||||
++pos; // eat closing brace
|
||||
if (variables.contains(varName))
|
||||
return variables[varName];
|
||||
else
|
||||
return {};
|
||||
} else {
|
||||
// unterminated
|
||||
return {};
|
||||
}
|
||||
} else if (command[pos] == '(') {
|
||||
// case 2, read to matching closing paren
|
||||
QString result = "$(";
|
||||
++pos; // eat opening paren
|
||||
int level = 1;
|
||||
while (pos < command.length() && level > 0) {
|
||||
if (command[pos] == '(')
|
||||
++level;
|
||||
else if (command[pos] == ')')
|
||||
--level;
|
||||
result.push_back(command[pos]);
|
||||
++pos;
|
||||
}
|
||||
return result;
|
||||
} else if (ansiCQuotingPermitted && command[pos] == '\'') {
|
||||
// case 3, parse ANSI-C quoting
|
||||
QByteArray unescaped;
|
||||
++pos; // eat opening quote
|
||||
while (pos < command.length()) {
|
||||
if (command[pos] == '\\') {
|
||||
++pos;
|
||||
if (pos < command.length()) {
|
||||
switch (command[pos].unicode()) {
|
||||
case 'a':
|
||||
++pos;
|
||||
unescaped.push_back('\a');
|
||||
break;
|
||||
case 'b':
|
||||
++pos;
|
||||
unescaped.push_back('\b');
|
||||
break;
|
||||
case 'e':
|
||||
case 'E':
|
||||
++pos;
|
||||
unescaped.push_back('\x1B');
|
||||
break;
|
||||
case 'f':
|
||||
++pos;
|
||||
unescaped.push_back('\f');
|
||||
break;
|
||||
case 'n':
|
||||
++pos;
|
||||
unescaped.push_back('\n');
|
||||
break;
|
||||
case 'r':
|
||||
++pos;
|
||||
unescaped.push_back('\r');
|
||||
break;
|
||||
case 't':
|
||||
++pos;
|
||||
unescaped.push_back('\t');
|
||||
break;
|
||||
case 'v':
|
||||
++pos;
|
||||
unescaped.push_back('\v');
|
||||
break;
|
||||
case '\\':
|
||||
++pos;
|
||||
unescaped.push_back('\\');
|
||||
break;
|
||||
case '\'':
|
||||
++pos;
|
||||
unescaped.push_back('\'');
|
||||
break;
|
||||
case '"':
|
||||
++pos;
|
||||
unescaped.push_back('"');
|
||||
break;
|
||||
case '?':
|
||||
++pos;
|
||||
unescaped.push_back('?');
|
||||
break;
|
||||
case '0':
|
||||
case '1':
|
||||
case '2':
|
||||
case '3':
|
||||
case '4':
|
||||
case '5':
|
||||
case '6':
|
||||
case '7': {
|
||||
QString digits = readOctal(command, pos);
|
||||
int octal = digits.toUInt(nullptr, 8);
|
||||
unescaped.push_back(octal);
|
||||
break;
|
||||
}
|
||||
case 'x': {
|
||||
++pos; // eat 'x'
|
||||
QString digits = readHex(command, pos, 2);
|
||||
if (digits.isEmpty()) {
|
||||
// normal character
|
||||
unescaped.append("\\x");
|
||||
} else {
|
||||
int hex = digits.toUInt(nullptr, 16);
|
||||
unescaped.push_back(hex);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'u': {
|
||||
++pos; // eat 'u'
|
||||
QString digits = readHex(command, pos, 4);
|
||||
if (digits.isEmpty()) {
|
||||
// normal character
|
||||
unescaped.append("\\u");
|
||||
} else {
|
||||
int hex = digits.toUInt(nullptr, 16);
|
||||
QByteArray encoded = QString(hex).toUtf8();
|
||||
unescaped.append(encoded);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'U': {
|
||||
++pos; // eat 'U'
|
||||
QString digits = readHex(command, pos, 8);
|
||||
if (digits.isEmpty()) {
|
||||
// normal character
|
||||
unescaped.append("\\U");
|
||||
} else {
|
||||
int hex = digits.toUInt(nullptr, 16);
|
||||
QByteArray encoded = QString(hex).toUtf8();
|
||||
unescaped.append(encoded);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
// normal character
|
||||
unescaped.push_back('\\');
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (command[pos] == '\'') {
|
||||
++pos; // eat closing quote
|
||||
return unescaped;
|
||||
} else {
|
||||
QChar c = command[pos];
|
||||
QByteArray encoded = QString(c).toUtf8();
|
||||
unescaped.append(encoded);
|
||||
++pos;
|
||||
}
|
||||
}
|
||||
// unterminated
|
||||
return unescaped;
|
||||
} else if (command[pos].isLetter() || command[pos] == '_') {
|
||||
// case 4, read variable name
|
||||
QString varName;
|
||||
while (pos < command.length() && (command[pos].isLetterOrNumber() || command[pos] == '_')) {
|
||||
varName.push_back(command[pos]);
|
||||
++pos;
|
||||
}
|
||||
if (variables.contains(varName))
|
||||
return variables[varName];
|
||||
else
|
||||
return {};
|
||||
} else {
|
||||
// case 5, return as is
|
||||
return "$";
|
||||
}
|
||||
}
|
||||
|
||||
/*Before:
|
||||
<VARNAME.abc$>
|
||||
^pos
|
||||
After:
|
||||
<VARNAME.abc$>
|
||||
^pos */
|
||||
QString devCppExpansion(const QString &command, int &pos, const QMap<QString, QString> &variables)
|
||||
{
|
||||
QString varName;
|
||||
QString result;
|
||||
while (pos < command.length() && command[pos] != '>') {
|
||||
varName.push_back(command[pos]);
|
||||
++pos;
|
||||
}
|
||||
if (pos < command.length()) {
|
||||
++pos; // eat closing bracket
|
||||
if (variables.contains(varName))
|
||||
return variables[varName];
|
||||
else
|
||||
// not a variable
|
||||
return '<' + varName + '>';
|
||||
} else {
|
||||
// unterminated, treat it as a normal string
|
||||
return '<' + varName;
|
||||
}
|
||||
}
|
||||
|
||||
/*Before:
|
||||
"blah\"blah"
|
||||
^pos
|
||||
After:
|
||||
"blah\"blah"
|
||||
^pos */
|
||||
QString doubleQuoted(const QString &command, int &pos, const QMap<QString, QString> &variables, bool enableDevCppVariableExpansion)
|
||||
{
|
||||
QString result;
|
||||
while (pos < command.length()) {
|
||||
switch (command[pos].unicode()) {
|
||||
case '$':
|
||||
++pos; // eat '$'
|
||||
result += variableExpansion(command, pos, variables, false);
|
||||
break;
|
||||
case '\\':
|
||||
++pos; // eat backslash
|
||||
if (pos < command.length()) {
|
||||
switch (command[pos].unicode()) {
|
||||
case '$':
|
||||
case '`':
|
||||
case '"':
|
||||
case '\\':
|
||||
result.push_back(command[pos]);
|
||||
++pos;
|
||||
break;
|
||||
case '\n':
|
||||
++pos; // eat newline
|
||||
break;
|
||||
default:
|
||||
// normal character
|
||||
result.push_back('\\');
|
||||
}
|
||||
} else {
|
||||
// unterminated
|
||||
result.push_back('\\');
|
||||
}
|
||||
break;
|
||||
case '"':
|
||||
++pos; // eat closing quote
|
||||
return result;
|
||||
case '<':
|
||||
if (enableDevCppVariableExpansion) {
|
||||
++pos; // eat '<'
|
||||
result += devCppExpansion(command, pos, variables);
|
||||
break;
|
||||
}
|
||||
[[fallthrough]];
|
||||
case '`': // not supported
|
||||
default:
|
||||
result.push_back(command[pos]);
|
||||
++pos;
|
||||
}
|
||||
}
|
||||
// unterminated
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace ParseArgumentsDetail
|
||||
|
||||
QStringList parseArguments(const QString &command, const QMap<QString, QString> &variables, bool enableDevCppVariableExpansion)
|
||||
{
|
||||
using namespace ParseArgumentsDetail;
|
||||
|
||||
QStringList result;
|
||||
QString current;
|
||||
bool currentPolluted = false;
|
||||
|
||||
int pos = 0;
|
||||
while (pos < command.length()) {
|
||||
switch (command[pos].unicode()) {
|
||||
case ' ':
|
||||
case '\t':
|
||||
case '\n':
|
||||
if (currentPolluted) {
|
||||
result.push_back(current);
|
||||
current.clear();
|
||||
currentPolluted = false;
|
||||
}
|
||||
++pos;
|
||||
break;
|
||||
case '#':
|
||||
if (currentPolluted) {
|
||||
// normal character
|
||||
current.push_back(command[pos]);
|
||||
++pos;
|
||||
} else {
|
||||
// comment, eat to newline
|
||||
while (pos < command.length() && command[pos] != '\n')
|
||||
++pos;
|
||||
}
|
||||
break;
|
||||
case '\'':
|
||||
++pos; // eat opening quote
|
||||
current += singleQuoted(command, pos);
|
||||
currentPolluted = true;
|
||||
break;
|
||||
case '"':
|
||||
++pos; // eat opening quote
|
||||
current += doubleQuoted(command, pos, variables, enableDevCppVariableExpansion);
|
||||
currentPolluted = true;
|
||||
break;
|
||||
case '$':
|
||||
++pos; // eat '$'
|
||||
current += variableExpansion(command, pos, variables, true);
|
||||
currentPolluted = true;
|
||||
break;
|
||||
case '\\':
|
||||
++pos; // eat backslash
|
||||
if (pos < command.length()) {
|
||||
if (command[pos] != '\n')
|
||||
++pos; // eat newline
|
||||
else {
|
||||
// normal character
|
||||
current.push_back(command[pos]);
|
||||
}
|
||||
++pos;
|
||||
currentPolluted = true;
|
||||
} else {
|
||||
// unterminated
|
||||
current.push_back('\\');
|
||||
}
|
||||
break;
|
||||
case '<':
|
||||
if (enableDevCppVariableExpansion) {
|
||||
++pos; // eat '<'
|
||||
current += devCppExpansion(command, pos, variables);
|
||||
currentPolluted = true;
|
||||
break;
|
||||
}
|
||||
[[fallthrough]];
|
||||
default:
|
||||
current.push_back(command[pos]);
|
||||
++pos;
|
||||
currentPolluted = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (currentPolluted)
|
||||
result.push_back(current);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
QStringList parseArgumentsWithoutVariables(const QString &command)
|
||||
{
|
||||
return parseArguments(command, {}, false);
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* Copyright (C) 2020-2022 Roy Qu (royqh1979@gmail.com)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#ifndef PARSEARG_H
|
||||
#define PARSEARG_H
|
||||
|
||||
#include <QString>
|
||||
#include <QMap>
|
||||
|
||||
QStringList parseArguments(const QString &command, const QMap<QString, QString> &variables, bool enableDevCppVariableExpansion);
|
||||
QStringList parseArgumentsWithoutVariables(const QString &command);
|
||||
|
||||
#endif // PARSEARG_H
|
|
@ -47,7 +47,9 @@ target("RedPandaIDE")
|
|||
-- problems
|
||||
"problems/freeprojectsetformat.cpp",
|
||||
"problems/ojproblemset.cpp",
|
||||
"problems/problemcasevalidator.cpp")
|
||||
"problems/problemcasevalidator.cpp",
|
||||
"utils/escape.cpp",
|
||||
"utils/parsearg.cpp")
|
||||
|
||||
add_moc_classes(
|
||||
"caretlist",
|
||||
|
@ -236,3 +238,13 @@ target("RedPandaIDE")
|
|||
if is_xdg() then
|
||||
on_install(install_bin)
|
||||
end
|
||||
|
||||
target("test-escape")
|
||||
set_kind("binary")
|
||||
add_rules("qt.console")
|
||||
|
||||
set_default(false)
|
||||
add_tests("test-escape")
|
||||
|
||||
add_files("utils/escape.cpp", "test/escape.cpp")
|
||||
add_includedirs(".")
|
||||
|
|
|
@ -539,7 +539,7 @@ QString changeFileExt(const QString& filename, QString ext)
|
|||
path = includeTrailingPathDelimiter(fileInfo.path());
|
||||
}
|
||||
if (suffix.isEmpty()) {
|
||||
return path+filename+ext;
|
||||
return path+name+ext;
|
||||
} else {
|
||||
return path+fileInfo.completeBaseName()+ext;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue