Merge branch 'master' of github.com:royqh1979/RedPanda-CPP

This commit is contained in:
Roy Qu 2024-02-28 20:13:44 +08:00
commit 5eff32cee9
31 changed files with 1629 additions and 698 deletions

30
.github/workflows/unit.yml vendored Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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());

View File

@ -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;
}

View File

@ -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,123 +175,114 @@ 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()) {
if (!mProject->options().objectOutput.isEmpty()) {
QString fullResFile = includeTrailingPathDelimiter(mProject->options().objectOutput) +
changeFileExt(mProject->options().privateResource, RES_EXT);
if (!mProject->options().objectOutput.isEmpty()) {
QString fullResFile = includeTrailingPathDelimiter(mProject->options().objectOutput) +
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());
} 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());
}
}
QString relativeResFile = extractRelativePath(mProject->directory(), fullResFile);
objResFile = relativeResFile;
cleanRes = localizePath(changeFileExt(relativeResFile, RES_EXT));
} else {
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)));
}
#ifdef Q_OS_WIN
writeln(file,"WINDRESFLAGS = " + mProject->options().resourceCmd);
#endif
writeln(file, "PCH_H = " + escapeFilenameForMakefilePrerequisite(pchH));
writeln(file, "PCH = " + escapeFilenameForMakefilePrerequisite(pch));
}
// 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);
}
@ -564,39 +575,43 @@ bool ProjectCompiler::prepareForCompile()
QString parallelParam;
if (mProject->options().allowParallelBuilding) {
if (mProject->options().parellelBuildingJobs==0) {
parallelParam = " --jobs";
parallelParam = "--jobs";
} else {
parallelParam = QString(" -j%1").arg(mProject->options().parellelBuildingJobs);
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;

View File

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

View File

@ -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,34 +226,36 @@ 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);
writeln(file, objStr);
// Write custom build command
if (unit->overrideBuildCmd() && !unit->buildCmd().isEmpty()) {
@ -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));
}
}
}
@ -323,39 +310,44 @@ bool SDCCProjectCompiler::prepareForCompile()
QString parallelParam;
if (mProject->options().allowParallelBuilding) {
if (mProject->options().parellelBuildingJobs==0) {
parallelParam = " --jobs";
parallelParam = "--jobs";
} else {
parallelParam = QString(" -j%1").arg(mProject->options().parellelBuildingJobs);
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;

View File

@ -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;
}

View File

@ -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());

View File

@ -29,6 +29,7 @@
#include <QDir>
#include <QScreen>
#include <QLockFile>
#include <QFontDatabase>
#include "common.h"
#include "colorscheme.h"
#include "iconsmanager.h"

View File

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

View File

@ -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\"");
}

View File

@ -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"
},
{

View File

@ -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 &param : 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 &param : 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);
@ -4122,7 +4124,12 @@ void Settings::Environment::checkAndSetTerminal()
QMessageBox::critical(
nullptr,
QCoreApplication::translate("Settings","Error"),
QCoreApplication::translate("Settings","Can't find terminal program!"));
QCoreApplication::translate("Settings","Can't find terminal program!"));
}
QMap<QString, QString> Settings::Environment::terminalArgsPatternMagicVariables()
{
return mTerminalArgsPatternMagicVariables;
}
QList<Settings::Environment::TerminalItem> Settings::Environment::loadTerminalList() const
@ -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);
}

View File

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

View File

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

View File

@ -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());
}

View File

@ -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));
}
}
}

View File

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

157
RedPandaIDE/test/escape.cpp Normal file
View File

@ -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&ampersand");
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;
}

View File

@ -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,36 +140,50 @@ bool programHasConsole(const QString & filename)
}
QString parseMacros(const QString &s)
{
return parseMacros(s, devCppMacroVariables());
}
QString parseMacros(const QString &s, const QMap<QString, QString> &macros)
{
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()));
if (compilerSet->defaultCppIncludeDirs().count() > 0)
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()));
if (compilerSet->defaultLibDirs().count() > 0)
result["LIB"] = localizePath(compilerSet->defaultLibDirs().front());
else
result.replace("<LIB>","");
result["LIB"] = "";
}
if (e!=nullptr && !e->inProject()) { // Non-project editor macros
if (e != nullptr && !e->inProject()) { // Non-project editor macros
QString exeSuffix;
Settings::PCompilerSet compilerSet = pSettings->compilerSets().defaultSet();
if (compilerSet) {
@ -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());
if (e != nullptr) {
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 &params, const QString &workingDir, const QString &tempFile)
void executeFile(const QString &fileName, const QStringList &params, 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, bashs 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 its 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
}

View File

@ -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();

View File

@ -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, bashs 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 its 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(' ');
}

View File

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

View File

@ -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);
}

View File

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

View File

@ -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(".")

View File

@ -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;
}