diff --git a/outputview/outputexecutejob.cpp b/outputview/outputexecutejob.cpp index 428b25af0..ca15e1a91 100644 --- a/outputview/outputexecutejob.cpp +++ b/outputview/outputexecutejob.cpp @@ -1,517 +1,520 @@ /* This file is part of KDevelop Copyright 2012 Ivan Shapovalov This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "outputexecutejob.h" #include "outputmodel.h" #include "outputdelegate.h" #include "debug.h" #include #include #include #include #include #include #include namespace KDevelop { class OutputExecuteJobPrivate { public: OutputExecuteJobPrivate( KDevelop::OutputExecuteJob* owner ); void childProcessStdout(); void childProcessStderr(); void emitProgress(const IFilterStrategy::Progress& progress); QString joinCommandLine() const; QString getJobName(); template< typename T > static void mergeEnvironment( QProcessEnvironment& dest, const T& src ); QProcessEnvironment effectiveEnvironment() const; QStringList effectiveCommandLine() const; OutputExecuteJob* m_owner; KProcess* m_process; ProcessLineMaker* m_lineMaker; OutputExecuteJob::JobStatus m_status; OutputExecuteJob::JobProperties m_properties; OutputModel::OutputFilterStrategy m_filteringStrategy; QScopedPointer m_filteringStrategyPtr; QStringList m_arguments; QStringList m_privilegedExecutionCommand; QUrl m_workingDirectory; QString m_environmentProfile; QHash m_environmentOverrides; QString m_jobName; bool m_outputStarted; }; OutputExecuteJobPrivate::OutputExecuteJobPrivate( OutputExecuteJob* owner ) : m_owner( owner ), m_process( new KProcess( m_owner ) ), m_lineMaker( new ProcessLineMaker( m_owner ) ), // do not assign process to the line maker as we'll feed it data ourselves m_status( OutputExecuteJob::JobNotStarted ), m_properties( OutputExecuteJob::DisplayStdout ), m_filteringStrategy( OutputModel::NoFilter ), m_outputStarted( false ) { } OutputExecuteJob::OutputExecuteJob( QObject* parent, OutputJob::OutputJobVerbosity verbosity ): OutputJob( parent, verbosity ), d( new OutputExecuteJobPrivate( this ) ) { d->m_process->setOutputChannelMode( KProcess::SeparateChannels ); connect( d->m_process, static_cast(&KProcess::finished), this, &OutputExecuteJob::childProcessExited ); connect( d->m_process, static_cast(&KProcess::error), this, &OutputExecuteJob::childProcessError ); connect( d->m_process, &KProcess::readyReadStandardOutput, this, [=] { d->childProcessStdout(); } ); connect( d->m_process, &KProcess::readyReadStandardError, this, [=] { d->childProcessStderr(); } ); } OutputExecuteJob::~OutputExecuteJob() { if( d->m_process->state() != QProcess::NotRunning ) { doKill(); } Q_ASSERT( d->m_process->state() == QProcess::NotRunning ); delete d; } OutputExecuteJob::JobStatus OutputExecuteJob::status() const { return d->m_status; } OutputModel* OutputExecuteJob::model() const { return dynamic_cast ( OutputJob::model() ); } QStringList OutputExecuteJob::commandLine() const { return d->m_arguments; } OutputExecuteJob& OutputExecuteJob::operator<<( const QString& argument ) { d->m_arguments << argument; return *this; } OutputExecuteJob& OutputExecuteJob::operator<<( const QStringList& arguments ) { d->m_arguments << arguments; return *this; } QStringList OutputExecuteJob::privilegedExecutionCommand() const { return d->m_privilegedExecutionCommand; } void OutputExecuteJob::setPrivilegedExecutionCommand( const QStringList& command ) { d->m_privilegedExecutionCommand = command; } void OutputExecuteJob::setJobName( const QString& name ) { d->m_jobName = name; QString jobName = d->getJobName(); setObjectName( jobName ); setTitle( jobName ); } QUrl OutputExecuteJob::workingDirectory() const { return d->m_workingDirectory; } void OutputExecuteJob::setWorkingDirectory( const QUrl& url ) { d->m_workingDirectory = url; } void OutputExecuteJob::start() { Q_ASSERT( d->m_status == JobNotStarted ); d->m_status = JobRunning; const bool isBuilder = d->m_properties.testFlag( IsBuilderHint ); const QUrl effectiveWorkingDirectory = workingDirectory(); if( effectiveWorkingDirectory.isEmpty() ) { if( d->m_properties.testFlag( NeedWorkingDirectory ) ) { // A directory is not given, but we need it. setError( InvalidWorkingDirectoryError ); if( isBuilder ) { setErrorText( i18n( "No build directory specified for a builder job." ) ); } else { setErrorText( i18n( "No working directory specified for a process." ) ); } return emitResult(); } setModel( new OutputModel ); } else { // Basic sanity checks. if( !effectiveWorkingDirectory.isValid() ) { setError( InvalidWorkingDirectoryError ); if( isBuilder ) { setErrorText( i18n( "Invalid build directory '%1'", effectiveWorkingDirectory.toDisplayString(QUrl::PreferLocalFile) ) ); } else { setErrorText( i18n( "Invalid working directory '%1'", effectiveWorkingDirectory.toDisplayString(QUrl::PreferLocalFile) ) ); } return emitResult(); } else if( !effectiveWorkingDirectory.isLocalFile() ) { setError( InvalidWorkingDirectoryError ); if( isBuilder ) { setErrorText( i18n( "Build directory '%1' is not a local path", effectiveWorkingDirectory.toDisplayString(QUrl::PreferLocalFile) ) ); } else { setErrorText( i18n( "Working directory '%1' is not a local path", effectiveWorkingDirectory.toDisplayString(QUrl::PreferLocalFile) ) ); } return emitResult(); } QFileInfo workingDirInfo( effectiveWorkingDirectory.toLocalFile() ); if( !workingDirInfo.isDir() ) { // If a working directory does not actually exist, either bail out or create it empty, // depending on what we need by properties. // We use a dedicated bool variable since !isDir() may also mean that it exists, // but is not a directory, or a symlink to an inexistent object. bool successfullyCreated = false; if( !d->m_properties.testFlag( CheckWorkingDirectory ) ) { successfullyCreated = QDir().mkdir( effectiveWorkingDirectory.toLocalFile() ); } if( !successfullyCreated ) { setError( InvalidWorkingDirectoryError ); if( isBuilder ) { setErrorText( i18n( "Build directory '%1' does not exist or is not a directory", effectiveWorkingDirectory.toDisplayString(QUrl::PreferLocalFile) ) ); } else { setErrorText( i18n( "Working directory '%1' does not exist or is not a directory", effectiveWorkingDirectory.toDisplayString(QUrl::PreferLocalFile) ) ); } return emitResult(); } } setModel( new OutputModel( effectiveWorkingDirectory ) ); } Q_ASSERT( model() ); if (d->m_filteringStrategy != OutputModel::NoFilter) { model()->setFilteringStrategy(d->m_filteringStrategy); } else { model()->setFilteringStrategy(d->m_filteringStrategyPtr.take()); } setDelegate( new OutputDelegate ); connect(model(), &OutputModel::progress, this, [&](const IFilterStrategy::Progress& progress) { d->emitProgress(progress); }); // Slots hasRawStdout() and hasRawStderr() are responsible // for feeding raw data to the line maker; so property-based channel filtering is implemented there. if( d->m_properties.testFlag( PostProcessOutput ) ) { connect( d->m_lineMaker, &ProcessLineMaker::receivedStdoutLines, this, &OutputExecuteJob::postProcessStdout ); connect( d->m_lineMaker, &ProcessLineMaker::receivedStderrLines, this, &OutputExecuteJob::postProcessStderr ); } else { connect( d->m_lineMaker, &ProcessLineMaker::receivedStdoutLines, model(), &OutputModel::appendLines ); connect( d->m_lineMaker, &ProcessLineMaker::receivedStderrLines, model(), &OutputModel::appendLines ); } if( !d->m_properties.testFlag( NoSilentOutput ) || verbosity() != Silent ) { d->m_outputStarted = true; startOutput(); } const QString joinedCommandLine = d->joinCommandLine(); QString headerLine; if( !effectiveWorkingDirectory.isEmpty() ) { headerLine = effectiveWorkingDirectory.toString( QUrl::PreferLocalFile | QUrl::StripTrailingSlash ) + "> " + joinedCommandLine; } else { headerLine = joinedCommandLine; } model()->appendLine( headerLine ); if( !effectiveWorkingDirectory.isEmpty() ) { d->m_process->setWorkingDirectory( effectiveWorkingDirectory.toLocalFile() ); } d->m_process->setProcessEnvironment( d->effectiveEnvironment() ); d->m_process->setProgram( d->effectiveCommandLine() ); qCDebug(OUTPUTVIEW) << "Starting:" << d->m_process->program().join(" ") << "in" << d->m_process->workingDirectory(); d->m_process->start(); } bool OutputExecuteJob::doKill() { const int terminateKillTimeout = 1000; // msecs if( d->m_status != JobRunning ) return true; d->m_status = JobCanceled; d->m_process->terminate(); bool terminated = d->m_process->waitForFinished( terminateKillTimeout ); if( !terminated ) { d->m_process->kill(); terminated = d->m_process->waitForFinished( terminateKillTimeout ); } d->m_lineMaker->flushBuffers(); if( terminated ) { model()->appendLine( i18n( "*** Killed process ***" ) ); } else { // It survived SIGKILL, leave it alone... model()->appendLine( i18n( "*** Warning: could not kill the process ***" ) ); } return true; } void OutputExecuteJob::childProcessError( QProcess::ProcessError processError ) { // This can be called twice: one time via an error() signal, and second - from childProcessExited(). // Avoid doing things in second time. if( d->m_status != OutputExecuteJob::JobRunning ) return; d->m_status = OutputExecuteJob::JobFailed; QString errorValue; switch( processError ) { case QProcess::FailedToStart: errorValue = i18n("%1 has failed to start", commandLine().first()); break; case QProcess::Crashed: errorValue = i18n("%1 has crashed", commandLine().first()); break; case QProcess::ReadError: errorValue = i18n("Read error"); break; case QProcess::WriteError: errorValue = i18n("Write error"); break; case QProcess::Timedout: errorValue = i18n("Waiting for the process has timed out"); break; default: case QProcess::UnknownError: errorValue = i18n("Exit code %1", d->m_process->exitCode()); break; } // Show the toolview if it's hidden for the user to be able to diagnose errors. if( !d->m_outputStarted ) { d->m_outputStarted = true; startOutput(); } setError( FailedShownError ); setErrorText( errorValue ); d->m_lineMaker->flushBuffers(); model()->appendLine( i18n("*** Failure: %1 ***", errorValue) ); emitResult(); } void OutputExecuteJob::childProcessExited( int exitCode, QProcess::ExitStatus exitStatus ) { if( d->m_status != JobRunning ) return; if( exitStatus == QProcess::CrashExit ) { childProcessError( QProcess::Crashed ); } else if ( exitCode != 0 ) { childProcessError( QProcess::UnknownError ); } else { d->m_status = JobSucceeded; d->m_lineMaker->flushBuffers(); model()->appendLine( i18n("*** Finished ***") ); emitResult(); } } void OutputExecuteJobPrivate::childProcessStdout() { QByteArray out = m_process->readAllStandardOutput(); if( m_properties.testFlag( OutputExecuteJob::DisplayStdout ) ) { m_lineMaker->slotReceivedStdout( out ); } } void OutputExecuteJobPrivate::childProcessStderr() { QByteArray err = m_process->readAllStandardError(); if( m_properties.testFlag( OutputExecuteJob::DisplayStderr ) ) { m_lineMaker->slotReceivedStderr( err ); } } void OutputExecuteJobPrivate::emitProgress(const IFilterStrategy::Progress& progress) { m_owner->emitPercent(progress.percent, 100); if (progress.percent == 100) { m_owner->infoMessage(m_owner, i18n("Build finished")); } else { m_owner->infoMessage(m_owner, progress.status); } } void OutputExecuteJob::postProcessStdout( const QStringList& lines ) { model()->appendLines( lines ); } void OutputExecuteJob::postProcessStderr( const QStringList& lines ) { model()->appendLines( lines ); } void OutputExecuteJob::setFilteringStrategy( OutputModel::OutputFilterStrategy strategy ) { d->m_filteringStrategy = strategy; // clear the other d->m_filteringStrategyPtr.reset(nullptr); } void OutputExecuteJob::setFilteringStrategy(IFilterStrategy* filterStrategy) { d->m_filteringStrategyPtr.reset(filterStrategy); // clear the other d->m_filteringStrategy = OutputModel::NoFilter; } OutputExecuteJob::JobProperties OutputExecuteJob::properties() const { return d->m_properties; } void OutputExecuteJob::setProperties( OutputExecuteJob::JobProperties properties, bool override ) { if( override ) { d->m_properties = properties; } else { d->m_properties |= properties; } } void OutputExecuteJob::unsetProperties( OutputExecuteJob::JobProperties properties ) { d->m_properties &= ~properties; } QString OutputExecuteJob::environmentProfile() const { return d->m_environmentProfile; } void OutputExecuteJob::setEnvironmentProfile( const QString& profile ) { d->m_environmentProfile = profile; } void OutputExecuteJob::addEnvironmentOverride( const QString& name, const QString& value ) { d->m_environmentOverrides[name] = value; } void OutputExecuteJob::removeEnvironmentOverride( const QString& name ) { d->m_environmentOverrides.remove( name ); } template< typename T > void OutputExecuteJobPrivate::mergeEnvironment( QProcessEnvironment& dest, const T& src ) { for( typename T::const_iterator it = src.begin(); it != src.end(); ++it ) { dest.insert( it.key(), it.value() ); } } QProcessEnvironment OutputExecuteJobPrivate::effectiveEnvironment() const { - QProcessEnvironment environment = QProcessEnvironment::systemEnvironment(); const EnvironmentGroupList environmentGroup( KSharedConfig::openConfig() ); QString environmentProfile = m_owner->environmentProfile(); if( environmentProfile.isEmpty() ) { environmentProfile = environmentGroup.defaultGroup(); } - OutputExecuteJobPrivate::mergeEnvironment( environment, environmentGroup.variables( environmentProfile ) ); + QProcessEnvironment environment = QProcessEnvironment::systemEnvironment(); + auto userEnv = environmentGroup.variables(environmentProfile); + expandVariables(userEnv, environment); + + OutputExecuteJobPrivate::mergeEnvironment( environment, userEnv ); OutputExecuteJobPrivate::mergeEnvironment( environment, m_environmentOverrides ); if( m_properties.testFlag( OutputExecuteJob::PortableMessages ) ) { environment.remove( "LC_ALL" ); environment.insert( "LC_MESSAGES", "C" ); } return environment; } QString OutputExecuteJobPrivate::joinCommandLine() const { return KShell::joinArgs( effectiveCommandLine() ); } QStringList OutputExecuteJobPrivate::effectiveCommandLine() const { // If we need to use a su-like helper, invoke it as // "helper -- our command line". QStringList privilegedCommand = m_owner->privilegedExecutionCommand(); if( !privilegedCommand.isEmpty() ) { return QStringList() << m_owner->privilegedExecutionCommand() << "--" << m_owner->commandLine(); } else { return m_owner->commandLine(); } } QString OutputExecuteJobPrivate::getJobName() { const QString joinedCommandLine = joinCommandLine(); if( m_properties.testFlag( OutputExecuteJob::AppendProcessString ) ) { if( !m_jobName.isEmpty() ) { return m_jobName + ": " + joinedCommandLine; } else { return joinedCommandLine; } } else { return m_jobName; } } } // namespace KDevelop #include "moc_outputexecutejob.cpp" diff --git a/util/environmentgrouplist.cpp b/util/environmentgrouplist.cpp index 0358070c6..e2e100b78 100644 --- a/util/environmentgrouplist.cpp +++ b/util/environmentgrouplist.cpp @@ -1,203 +1,223 @@ /* This file is part of KDevelop Copyright 2007 Andreas Pakulat This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "environmentgrouplist.h" #include #include #include #include +#include +#include namespace KDevelop { class EnvironmentGroupListPrivate { public: QMap > m_groups; QString m_defaultGroup; }; } using namespace KDevelop; namespace { namespace Strings { inline QString defaultEnvGroupKey() { return QStringLiteral("Default Environment Group"); } inline QString envGroup() { return QStringLiteral("Environment Settings"); } inline QString groupListKey() { return QStringLiteral("Group List"); } inline QString defaultGroup() { return QStringLiteral("default"); } } void decode( KConfig* config, EnvironmentGroupListPrivate* d ) { KConfigGroup cfg( config, Strings::envGroup() ); d->m_defaultGroup = cfg.readEntry( Strings::defaultEnvGroupKey(), Strings::defaultGroup() ); QStringList grouplist = cfg.readEntry( Strings::groupListKey(), QStringList{Strings::defaultGroup()} ); foreach( const QString &envgrpname, grouplist ) { KConfigGroup envgrp( &cfg, envgrpname ); QMap variables; foreach( const QString &varname, envgrp.keyList() ) { variables[varname] = envgrp.readEntry( varname, QString() ); } d->m_groups.insert( envgrpname, variables ); } } void encode( KConfig* config, EnvironmentGroupListPrivate* d ) { KConfigGroup cfg( config, Strings::envGroup() ); cfg.writeEntry( Strings::defaultEnvGroupKey(), d->m_defaultGroup ); cfg.writeEntry( Strings::groupListKey(), d->m_groups.keys() ); foreach( const QString &group, cfg.groupList() ) { if( !d->m_groups.keys().contains( group ) ) { cfg.deleteGroup( group ); } } foreach( const QString &group, d->m_groups.keys() ) { KConfigGroup envgrp( &cfg, group ); envgrp.deleteGroup(); foreach( const QString &var, d->m_groups[group].keys() ) { envgrp.writeEntry( var, d->m_groups[group][var] ); } } cfg.sync(); } } EnvironmentGroupList::EnvironmentGroupList( const EnvironmentGroupList& rhs ) : d( new EnvironmentGroupListPrivate( *rhs.d ) ) { } EnvironmentGroupList& EnvironmentGroupList::operator=( const EnvironmentGroupList& rhs ) { *d = *rhs.d; return *this; } EnvironmentGroupList::EnvironmentGroupList( KSharedConfigPtr config ) : d( new EnvironmentGroupListPrivate ) { decode( config.data(), d ); } EnvironmentGroupList::EnvironmentGroupList( KConfig* config ) : d( new EnvironmentGroupListPrivate ) { decode( config, d ); } EnvironmentGroupList::~EnvironmentGroupList() { delete d; } const QMap EnvironmentGroupList::variables( const QString& group ) const { return d->m_groups[group.isEmpty() ? d->m_defaultGroup : group]; } QMap& EnvironmentGroupList::variables( const QString& group ) { return d->m_groups[group.isEmpty() ? d->m_defaultGroup : group]; } QString EnvironmentGroupList::defaultGroup() const { return d->m_defaultGroup; } void EnvironmentGroupList::setDefaultGroup( const QString& group ) { if( group.isEmpty() ) { return; } if( d->m_groups.contains( group ) ) { d->m_defaultGroup = group; } } void EnvironmentGroupList::saveSettings( KConfig* config ) const { encode( config, d ); config->sync(); } void EnvironmentGroupList::loadSettings( KConfig* config ) { d->m_groups.clear(); decode( config, d ); } QStringList EnvironmentGroupList::groups() const { return d->m_groups.keys(); } void EnvironmentGroupList::removeGroup( const QString& group ) { d->m_groups.remove( group ); } EnvironmentGroupList::EnvironmentGroupList() : d( new EnvironmentGroupListPrivate ) { } QStringList EnvironmentGroupList::createEnvironment( const QString & group, const QStringList & defaultEnvironment ) const { QMap retMap; foreach( const QString &line, defaultEnvironment ) { QString varName = line.section( '=', 0, 0 ); QString varValue = line.section( '=', 1 ); retMap.insert( varName, varValue ); } if( !group.isEmpty() ) { QMap userMap = variables(group); for( QMap::const_iterator it = userMap.constBegin(); it != userMap.constEnd(); ++it ) { retMap.insert( it.key(), it.value() ); } } QStringList env; for( QMap::const_iterator it = retMap.constBegin(); it != retMap.constEnd(); ++it ) { env << it.key() + '=' + it.value(); } return env; } + +void KDevelop::expandVariables(QMap& variables, const QProcessEnvironment& environment) +{ + QRegularExpression rVar("(? This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDEVPLATFORM_ENVIRONMENTGROUPLIST_H #define KDEVPLATFORM_ENVIRONMENTGROUPLIST_H #include "utilexport.h" #include +class QProcessEnvironment; class KConfig; template class QMap; class QString; class QStringList; namespace KDevelop { /** * This class manages a list of environment groups, each group containing a number * of environment variables and their values. * * The class is constructed from a KConfig object for easy usage in the plugins. * * The methods to change the environments is protected to disallow access to those methods * from plugins, only the environment widget is allowed to change them. * * Example Usage * \code * KSharedConfigPtr config = KSharedConfig::openConfig(); * EnvironmentGroupList env(config); * KConfigGroup cfg(config, "QMake Builder"); * QMap myenvVars = env.variables( cfg.readEntry("QMake Environment") ); * \endcode * * Two entries are used by this class: * "Default Environment Group" and "Environment Variables". * * "Default Environment Variables" stores the default group that should be used if the * user didn't select a group via a plugins configuration dialog. * * "Environment Variables" entry stores the actual list of * . The groupname can't contain '%' or '='. * For example, suppose that two configuration, say "release" and "debug" exist. * Then the actual contents of .kdev4 project file will be * * \code * [Environment Settings] * Default Environment Group=debug * Environment Variables=debug_PATH=/home/kde-devel/usr/bin,release_PATH=/usr/bin * \endcode * */ class KDEVPLATFORMUTIL_EXPORT EnvironmentGroupList { public: EnvironmentGroupList( const EnvironmentGroupList& rhs ); EnvironmentGroupList& operator=( const EnvironmentGroupList& rhs ); /** * Creates an a list of EnvironmentGroups from a KConfig object * @param config the KConfig object to read the environment groups from */ explicit EnvironmentGroupList( KSharedConfigPtr config ); explicit EnvironmentGroupList( KConfig* config ); ~EnvironmentGroupList(); /** * Creates a merged environment between the defaults specified by * \a defaultEnvironment and those saved in \a group */ QStringList createEnvironment(const QString& group, const QStringList& defaultEnvironment ) const; /** * returns the variables that are set for a given group. * This function provides read-only access to the environment * @param group the name of the group for which the environment should be returned * @return a map containing the environment variables for this group, or an empty map if the group doesn't exist in this list */ const QMap variables( const QString& group ) const; /** * returns the default group * The default group should be used by plugins unless the user chooses a different group * @return the name of the default group, defaults to "default" */ QString defaultGroup() const; /** * Fetch the list of known groups from the list * @return the list of groups */ QStringList groups() const; protected: EnvironmentGroupList(); /** * returns the variables that are set for a given group. * This function provides write access to the environment, so new variables can be inserted, existing ones changed or deleted * * If a non-existing group is specified this returns a new empty map and that way this function can be used to add a new group * to the list of environment groups * @param group the name of the group for which the environment should be returned * @return a map containing the environment variables for this group, or an empty map if the group doesn't exist in this list */ QMap& variables( const QString& group ); /** * Changes the default group. * @param group a new groupname, if a group of this name doesn't exist the default group is not changed */ void setDefaultGroup( const QString& group ); /** * Stores the environment groups in this list to the given KConfig object * @param config a KConfig object to which the environment settings should be stored */ void saveSettings( KConfig* config ) const; void loadSettings( KConfig* config ); void removeGroup( const QString& group ); private: class EnvironmentGroupListPrivate* const d; }; +KDEVPLATFORMUTIL_EXPORT void expandVariables(QMap& variables, const QProcessEnvironment& environment); + } #endif diff --git a/util/tests/CMakeLists.txt b/util/tests/CMakeLists.txt index bdf9c2ae0..fdff47a2d 100644 --- a/util/tests/CMakeLists.txt +++ b/util/tests/CMakeLists.txt @@ -1,20 +1,23 @@ ecm_add_test(test_embeddedfreetree.cpp LINK_LIBRARIES KF5::TextEditor Qt5::Test KDev::Language KDev::Tests) ecm_add_test(test_kdevvarlengtharray.cpp LINK_LIBRARIES Qt5::Test) ecm_add_test(test_objectlist.cpp LINK_LIBRARIES Qt5::Test KDev::Util) ecm_add_test(test_stringhandler.cpp LINK_LIBRARIES Qt5::Test KDev::Util) ecm_add_test(test_path.cpp LINK_LIBRARIES Qt5::Test KF5::KIOCore KDev::Tests KDev::Util) ecm_add_test(test_foregroundlock.cpp LINK_LIBRARIES Qt5::Test KDev::Util) ecm_add_test(test_executecompositejob.cpp LINK_LIBRARIES Qt5::Test KDev::Util) + +ecm_add_test(test_environment.cpp + LINK_LIBRARIES Qt5::Test KDev::Util) diff --git a/util/tests/test_environment.cpp b/util/tests/test_environment.cpp new file mode 100644 index 000000000..c20200dae --- /dev/null +++ b/util/tests/test_environment.cpp @@ -0,0 +1,81 @@ +/* + * This file is part of KDevelop + * + * Copyright 2015 Artur Puzio + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 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, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "test_environment.h" + +#include "util/environmentgrouplist.h" + +#include + +QTEST_MAIN(TestEnvironment); + +using ProcEnv = QMap; + +void TestEnvironment::testExpandVariables_data() +{ + QTest::addColumn("env"); + QTest::addColumn("expectedEnv"); + + QTest::newRow("no variables") << ProcEnv({}) << ProcEnv({}); + QTest::newRow("simple variables") << ProcEnv{ + {"VAR1","data"}, + {"Var2","some other data"} + } << ProcEnv({ + {"VAR1","data"}, + {"Var2","some other data"} + }); + QTest::newRow("PATH append and prepend") << ProcEnv({ + {"PATH","/home/usr/bin:$PATH:/home/user/folder"} + }) << ProcEnv({ + {"PATH", "/home/usr/bin:/bin:/usr/bin:/home/user/folder"} + }); + QTest::newRow("\\$VAR") << ProcEnv({ + {"MY_VAR","\\$PATH something \\$HOME"} + }) << ProcEnv({ + {"MY_VAR","$PATH something $HOME"} + }); + QTest::newRow("spaces, \\$VAR after $VAR") << ProcEnv({ + {"MY_VAR","$PATH:$HOME something \\$HOME"} + }) << ProcEnv({ + {"MY_VAR","/bin:/usr/bin:/home/tom something $HOME"} + }); + QTest::newRow("VAR2=$VAR1") << ProcEnv({ + {"VAR1","/some/path"},{"VAR2","$VAR1"} + }) << ProcEnv({ + {"VAR1","/some/path"},{"VAR2",""} + }); +} + +void TestEnvironment::testExpandVariables() +{ + QFETCH(ProcEnv, env); + QFETCH(ProcEnv, expectedEnv); + + QProcessEnvironment fakeSysEnv; + fakeSysEnv.insert("PATH","/bin:/usr/bin"); + fakeSysEnv.insert("HOME","/home/tom"); + + KDevelop::expandVariables(env, fakeSysEnv); + + for (auto it = expectedEnv.cbegin(); it!= expectedEnv.cend(); ++it) { + QCOMPARE(env.value(it.key()), it.value()); + } +} diff --git a/util/tests/test_environment.h b/util/tests/test_environment.h new file mode 100644 index 000000000..ce7b07e4a --- /dev/null +++ b/util/tests/test_environment.h @@ -0,0 +1,35 @@ +/* + * This file is part of KDevelop + * + * Copyright 2015 Artur Puzio + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 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, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +#ifndef TESTENVIRONMENT_H +#define TESTENVIRONMENT_H + +#include + +class TestEnvironment : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void testExpandVariables_data(); + void testExpandVariables(); +}; + +#endif // TESTENVIRONMENT_H