diff --git a/appimage/kdevelop-recipe-centos6.sh b/appimage/kdevelop-recipe-centos6.sh index f50377969e..c125a86ec5 100644 --- a/appimage/kdevelop-recipe-centos6.sh +++ b/appimage/kdevelop-recipe-centos6.sh @@ -1,574 +1,491 @@ #!/bin/bash # Halt on errors set -e # Be verbose set -x # Now we are inside CentOS 6 grep -r "CentOS release 6" /etc/redhat-release || exit 1 git_pull_rebase_helper() { git fetch git stash || true git rebase $(git rev-parse --abbrev-ref --symbolic-full-name @{u}) || true git stash pop || true } QTVERSION=5.7.1 QVERSION_SHORT=5.7 QTDIR=/usr/local/Qt-${QTVERSION}/ if [ -z "$KDEVELOP_VERSION" ]; then KDEVELOP_VERSION=5.1 fi if [ -z "$KDEV_PG_QT_VERSION" ]; then KDEV_PG_QT_VERSION=2.0 fi KF5_VERSION=v5.32.0 KDE_APPLICATION_VERSION=v16.12.3 GRANTLEE_VERSION=v5.1.0 export LLVM_ROOT=/opt/llvm/ export PATH=/opt/rh/python27/root/usr/bin/:$PATH export LD_LIBRARY_PATH=/opt/rh/python27/root/usr/lib64:$LD_LIBRARY_PATH # qjsonparser, used to add metadata to the plugins needs to work in a en_US.UTF-8 environment. That's # not always set correctly in CentOS 6.7 export LC_ALL=en_US.UTF-8 export LANG=en_us.UTF-8 # Determine which architecture should be built if [[ "$(arch)" = "i686" || "$(arch)" = "x86_64" ]] ; then ARCH=$(arch) else echo "Architecture could not be determined" exit 1 fi # Make sure we build from the /, parts of this script depends on that. We also need to run as root... cd / # Build AppImageKit rm -Rf /AppImageKit if [ ! -d AppImageKit ] ; then git clone --depth 1 https://github.com/probonopd/AppImageKit.git /AppImageKit fi cd /AppImageKit/ git checkout master git_pull_rebase_helper git reset --hard remotes/origin/stable/v1.0 ./build.sh cd / # Use the new compiler . /opt/rh/devtoolset-4/enable export CMAKE_PREFIX_PATH=$QTDIR:/kdevelop.appdir/share/llvm/ # if the library path doesn't point to our usr/lib, linking will be broken and we won't find all deps either export LD_LIBRARY_PATH=/usr/lib64/:/usr/lib:/kdevelop.appdir/usr/lib:$QTDIR/lib/:/opt/python3.6/lib/:$LD_LIBRARY_PATH # Workaround for: On CentOS 6, .pc files in /usr/lib/pkgconfig are not recognized # However, this is where .pc files get installed when bulding libraries... (FIXME) # I found this by comparing the output of librevenge's "make install" command # between Ubuntu and CentOS 6 ln -sf /usr/share/pkgconfig /usr/lib/pkgconfig -# Get kdevplatform -if [ ! -d /kdevplatform ] ; then - git clone --depth 1 http://anongit.kde.org/kdevplatform.git /kdevplatform -fi -cd /kdevplatform/ -git_pull_rebase_helper -git checkout $KDEVELOP_VERSION - -# Get kdevelop -if [ ! -d /kdevelop ] ; then - git clone --depth 1 http://anongit.kde.org/kdevelop.git /kdevelop -fi -cd /kdevelop/ -git_pull_rebase_helper -git checkout $KDEVELOP_VERSION - -# Get kdev-python -if [ ! -d /kdev-python ] ; then - git clone --depth 1 http://anongit.kde.org/kdev-python.git /kdev-python -fi -cd /kdev-python/ -git_pull_rebase_helper -git checkout $KDEVELOP_VERSION - -# Get kdev-pg-qt -if [ ! -d /kdevelop-pg-qt ] ; then - git clone --depth 1 http://anongit.kde.org/kdevelop-pg-qt /kdevelop-pg-qt -fi -cd /kdevelop-pg-qt -git_pull_rebase_helper -git checkout $KDEV_PG_QT_VERSION - -# Get kdev-php -if [ ! -d /kdev-php ] ; then - git clone --depth 1 http://anongit.kde.org/kdev-php /kdev-php -fi -cd /kdev-php -git_pull_rebase_helper -git checkout $KDEVELOP_VERSION - -# Get Grantlee -if [ ! -d /grantlee ]; then - git clone https://github.com/steveire/grantlee.git /grantlee -fi -cd /grantlee -git checkout master -git_pull_rebase_helper -git checkout $GRANTLEE_VERSION - # Prepare the install location rm -rf /kdevelop.appdir/ || true mkdir -p /kdevelop.appdir/usr # refresh ldconfig cache ldconfig # make sure lib and lib64 are the same thing mkdir -p /kdevelop.appdir/usr/lib cd /kdevelop.appdir/usr ln -s lib lib64 # start building the deps -function build_framework +function build_project { ( - SRC=/kf5 - BUILD=/kf5/build + SRC=$HOME/src/ + BUILD=$HOME/build PREFIX=/kdevelop.appdir/usr/ - # framework - FRAMEWORK=$1 + PROJECT=$1 + VERSION=$2 # clone if not there mkdir -p $SRC cd $SRC - if ( test -d $FRAMEWORK ) + if ( test -d $PROJECT ) then - echo "$FRAMEWORK already cloned" - cd $FRAMEWORK + echo "$PROJECT already cloned" + cd $PROJECT git stash git reset --hard git fetch git fetch --tags cd .. else - git clone git://anongit.kde.org/$FRAMEWORK + if [ -z "$CUSTOM_GIT_URL" ]; then + git clone git://anongit.kde.org/$PROJECT + else + git clone $CUSTOM_GIT_URL + fi fi - cd $FRAMEWORK - git checkout $KF5_VERSION || git checkout $KDE_APPLICATION_VERSION + cd $PROJECT + git checkout $VERSION git rebase $(git rev-parse --abbrev-ref --symbolic-full-name @{u}) || true # git rebase will fail if a tag is checked out git stash pop || true cd .. - if [ "$FRAMEWORK" = "knotifications" ]; then - cd $FRAMEWORK + if [ "$PROJECT" = "knotifications" ]; then + cd $PROJECT echo "patching knotifications" git reset --hard cat > no_phonon.patch << EOF diff --git a/CMakeLists.txt b/CMakeLists.txt index b97425f..8f15f08 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -59,10 +59,10 @@ find_package(KF5Config ${KF5_DEP_VERSION} REQUIRED) find_package(KF5Codecs ${KF5_DEP_VERSION} REQUIRED) find_package(KF5CoreAddons ${KF5_DEP_VERSION} REQUIRED) -find_package(Phonon4Qt5 4.6.60 REQUIRED NO_MODULE) +find_package(Phonon4Qt5 4.6.60 NO_MODULE) set_package_properties(Phonon4Qt5 PROPERTIES DESCRIPTION "Qt-based audio library" - TYPE REQUIRED + TYPE OPTIONAL PURPOSE "Required to build audio notification support") if (Phonon4Qt5_FOUND) add_definitions(-DHAVE_PHONON4QT5) EOF cat no_phonon.patch |patch -p1 cd .. fi # create build dir - mkdir -p $BUILD/$FRAMEWORK + mkdir -p $BUILD/$PROJECT # go there - cd $BUILD/$FRAMEWORK + cd $BUILD/$PROJECT # cmake it - cmake3 $SRC/$FRAMEWORK -DBUILD_TESTING=OFF -DCMAKE_INSTALL_PREFIX:PATH=$PREFIX $2 + cmake3 $SRC/$PROJECT -DBUILD_TESTING=OFF -DCMAKE_INSTALL_PREFIX:PATH=$PREFIX $3 # make make -j$(nproc) # install make install ) } +function build_framework +{ ( + build_project $1 $KF5_VERSION $2 +) } + build_framework extra-cmake-modules build_framework kconfig build_framework kguiaddons build_framework ki18n build_framework kitemviews build_framework sonnet build_framework kwindowsystem build_framework kwidgetsaddons build_framework kcompletion build_framework kdbusaddons build_framework karchive build_framework kcoreaddons build_framework kjobwidgets build_framework kcrash build_framework kservice build_framework kcodecs build_framework kauth build_framework kconfigwidgets build_framework kiconthemes build_framework ktextwidgets build_framework kglobalaccel build_framework kxmlgui build_framework kbookmarks build_framework solid build_framework kio build_framework kparts build_framework kitemmodels build_framework threadweaver build_framework attica build_framework knewstuff build_framework syntax-highlighting build_framework ktexteditor build_framework kpackage build_framework kdeclarative build_framework kcmutils build_framework knotifications build_framework knotifyconfig -build_framework libkomparediff2 build_framework kdoctools build_framework breeze-icons -DBINARY_ICONS_RESOURCE=1 build_framework kpty build_framework kinit -build_framework konsole -cd /grantlee -mkdir -p build -cd build -cmake3 .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/kdevelop.appdir/usr -make -j$(nproc) install +build_project libkomparediff2 $KDE_APPLICATION_VERSION +build_project kate $KDE_APPLICATION_VERSION # for snippet plugin, see T3826 +build_project konsole $KDE_APPLICATION_VERSION -cd / +(CUSTOM_GIT_URL=https://github.com/steveire/grantlee.git build_project grantlee $GRANTLEE_VERSION) -# Build kdev-pg-qt -mkdir -p /kdevelop-pg-qt_build -cd /kdevelop-pg-qt_build -cmake3 ../kdevelop-pg-qt \ - -DCMAKE_INSTALL_PREFIX:PATH=/kdevelop.appdir/usr \ - -DCMAKE_BUILD_TYPE=RelWithDebInfo -make -j$(nproc) install - -# Build KDevPlatform -mkdir -p /kdevplatform_build -cd /kdevplatform_build -cmake3 ../kdevplatform \ - -DCMAKE_INSTALL_PREFIX:PATH=/kdevelop.appdir/usr/ \ - -DCMAKE_BUILD_TYPE=RelWithDebInfo \ - -DBUILD_TESTING=FALSE -make -j$(nproc) install -# no idea why this is required but otherwise kdevelop picks it up and fails -rm /kdevplatform_build/KDevPlatformConfig.cmake - -# Build KDevelop -mkdir -p /kdevelop_build -cd /kdevelop_build -cmake3 ../kdevelop \ - -DCMAKE_INSTALL_PREFIX:PATH=/kdevelop.appdir/usr/ \ - -DCMAKE_BUILD_TYPE=RelWithDebInfo \ - -DBUILD_TESTING=FALSE -make -j$(nproc) install -rm /kdevelop_build/KDevelopConfig.cmake - -# for python -export LD_LIBRARY_PATH=$LD_LIBRARY_PATH/kdevelop.appdir/usr/lib/ -# Build kdev-python -mkdir -p /kdev-python_build -cd /kdev-python_build -cmake3 ../kdev-python \ - -DCMAKE_INSTALL_PREFIX:PATH=/kdevelop.appdir/usr/ \ - -DCMAKE_BUILD_TYPE=RelWithDebInfo \ - -DBUILD_TESTING=FALSE -make -j$(nproc) install - -# Build kdev-php -mkdir -p /kdev-php_build -cd /kdev-php_build -cmake3 ../kdev-php \ - -DCMAKE_INSTALL_PREFIX:PATH=/kdevelop.appdir/usr \ - -DCMAKE_BUILD_TYPE=RelWithDebInfo -make -j$(nproc) install +build_project kdevelop-pg-qt $KDEV_PG_QT_VERSION +build_project kdevplatform $KDEVELOP_VERSION +build_project kdevelop $KDEVELOP_VERSION +build_project kdev-php $KDEVELOP_VERSION +# Build kdev-python +export LD_LIBRARY_PATH=$LD_LIBRARY_PATH/kdevelop.appdir/usr/lib/ +build_project kdev-python $KDEVELOP_VERSION cd /kdevelop.appdir # FIXME: How to find out which subset of plugins is really needed? I used strace when running the binary mkdir -p ./usr/lib/qt5/plugins/ if [ -e $(dirname $QTDIR/plugins/bearer) ] ; then PLUGINS=$(dirname $QTDIR/plugins/bearer) else PLUGINS=../../$QTVERSION_SHORT/gc*/plugins/ fi echo $PLUGINS # /usr/lib64/qt5/plugins if build system Qt is found cp -r $PLUGINS/bearer ./usr/lib/qt5/plugins/ cp -r $PLUGINS/generic ./usr/lib/qt5/plugins/ cp -r $PLUGINS/imageformats ./usr/lib/qt5/plugins/ cp -r $PLUGINS/platforms ./usr/lib/qt5/plugins/ cp -r $PLUGINS/iconengines ./usr/lib/qt5/plugins/ cp -r $PLUGINS/platforminputcontexts ./usr/lib/qt5/plugins/ # cp -r $PLUGINS/platformthemes ./usr/lib/qt5/plugins/ cp -r $PLUGINS/xcbglintegrations ./usr/lib/qt5/plugins/ cp -R /kdevelop.appdir/usr/lib/grantlee/ /kdevelop.appdir/usr/lib/qt5/plugins/ rm -Rf /kdevelop.appdir/usr/lib/grantlee cp -ru /usr/share/mime/* /kdevelop.appdir/usr/share/mime update-mime-database /kdevelop.appdir/usr/share/mime/ cp -R ./usr/lib/plugins/* ./usr/lib/qt5/plugins/ rm -Rf ./usr/lib/plugins/ cp $(ldconfig -p | grep libsasl2.so.2 | cut -d ">" -f 2 | xargs) ./usr/lib/ # Fedora 23 seemed to be missing SOMETHING from the Centos 6.7. The only message was: # This application failed to start because it could not find or load the Qt platform plugin "xcb". # Setting export QT_DEBUG_PLUGINS=1 revealed the cause. # QLibraryPrivate::loadPlugin failed on "/usr/lib64/qt5/plugins/platforms/libqxcb.so" : # "Cannot load library /usr/lib64/qt5/plugins/platforms/libqxcb.so: (/lib64/libEGL.so.1: undefined symbol: drmGetNodeTypeFromFd)" # Which means that we have to copy libEGL.so.1 in too cp $(ldconfig -p | grep libEGL.so.1 | cut -d ">" -f 2 | xargs) ./usr/lib/ # Otherwise F23 cannot load the Qt platform plugin "xcb" cp $(ldconfig -p | grep libxcb.so.1 | cut -d ">" -f 2 | xargs) ./usr/lib/ cp $(ldconfig -p | grep libfreetype.so.6 | cut -d ">" -f 2 | xargs) ./usr/lib/ # For Fedora 20 ldd usr/bin/kdevelop | grep "=>" | awk '{print $3}' | xargs -I '{}' cp -v '{}' ./usr/lib || true cp /usr/bin/cmake usr/bin/cmake ldd usr/bin/cmake | grep "=>" | awk '{print $3}' | xargs -I '{}' cp -v '{}' ./usr/lib || true #ldd usr/lib64/kdevelop/*.so | grep "=>" | awk '{print $3}' | xargs -I '{}' cp -v '{}' ./usr/lib || true #ldd usr/lib64/plugins/imageformats/*.so | grep "=>" | awk '{print $3}' | xargs -I '{}' cp -v '{}' ./usr/lib || true ldd usr/lib/qt5/plugins/platforms/libqxcb.so | grep "=>" | awk '{print $3}' | xargs -I '{}' cp -v '{}' ./usr/lib || true # Copy in the indirect dependencies FILES=$(find . -type f -executable) for FILE in $FILES ; do echo "*** Processing:" $FILE ldd "${FILE}" | grep "=>" | awk '{print $3}' | xargs -I '{}' cp -vu '{}' usr/lib || true done # The following are assumed to be part of the base system rm -f usr/lib/libcom_err.so.2 || true rm -f usr/lib/libcrypt.so.1 || true rm -f usr/lib/libdl.so.2 || true rm -f usr/lib/libexpat.so.1 || true #rm -f usr/lib/libfontconfig.so.1 || true rm -f usr/lib/libgcc_s.so.1 || true rm -f usr/lib/libglib-2.0.so.0 || true rm -f usr/lib/libgpg-error.so.0 || true rm -f usr/lib/libgssapi_krb5.so.2 || true rm -f usr/lib/libgssapi.so.3 || true rm -f usr/lib/libhcrypto.so.4 || true rm -f usr/lib/libheimbase.so.1 || true rm -f usr/lib/libheimntlm.so.0 || true rm -f usr/lib/libhx509.so.5 || true rm -f usr/lib/libICE.so.6 || true rm -f usr/lib/libidn.so.11 || true rm -f usr/lib/libk5crypto.so.3 || true rm -f usr/lib/libkeyutils.so.1 || true rm -f usr/lib/libkrb5.so.26 || true rm -f usr/lib/libkrb5.so.3 || true rm -f usr/lib/libkrb5support.so.0 || true # rm -f usr/lib/liblber-2.4.so.2 || true # needed for debian wheezy # rm -f usr/lib/libldap_r-2.4.so.2 || true # needed for debian wheezy rm -f usr/lib/libm.so.6 || true rm -f usr/lib/libp11-kit.so.0 || true rm -f usr/lib/libpcre.so.3 || true rm -f usr/lib/libpthread.so.0 || true rm -f usr/lib/libresolv.so.2 || true rm -f usr/lib/libroken.so.18 || true rm -f usr/lib/librt.so.1 || true rm -f usr/lib/libSM.so.6 || true rm -f usr/lib/libusb-1.0.so.0 || true rm -f usr/lib/libuuid.so.1 || true rm -f usr/lib/libwind.so.0 || true # Remove these libraries, we need to use the system versions; this means 11.04 is not supported (12.04 is our baseline) rm -f usr/lib/libGL.so.* || true rm -f usr/lib/libdrm.so.* || true #rm -f usr/lib/libz.so.1 || true # These seem to be available on most systems but not Ubuntu 11.04 # rm -f usr/lib/libffi.so.6 usr/lib/libGL.so.1 usr/lib/libglapi.so.0 usr/lib/libxcb.so.1 usr/lib/libxcb-glx.so.0 || true # Delete potentially dangerous libraries rm -f usr/lib/libstdc* usr/lib/libgobject* usr/lib/libc.so.* || true # Do NOT delete libX* because otherwise on Ubuntu 11.04: # loaded library "Xcursor" malloc.c:3096: sYSMALLOc: Assertion (...) Aborted # We don't bundle the developer stuff rm -rf usr/include || true rm -rf usr/lib/cmake || true rm -rf usr/lib/pkgconfig || true rm -rf usr/share/ECM/ || true rm -rf usr/share/gettext || true rm -rf usr/share/pkgconfig || true strip -g $(find usr) || true # We do not bundle this, so let's not search that inside the AppImage. # Fixes "Qt: Failed to create XKB context!" and lets us enter text #sed -i -e 's|././/share/X11/|/usr/share/X11/|g' ./usr/lib/qt5/plugins/platforminputcontexts/libcomposeplatforminputcontextplugin.so #sed -i -e 's|././/share/X11/|/usr/share/X11/|g' ./usr/lib/libQt5XcbQpa.so.5 # Workaround for: # D-Bus library appears to be incorrectly set up; # failed to read machine uuid: Failed to open # The file is more commonly in /etc/machine-id # sed -i -e 's|/var/lib/dbus/machine-id|//././././etc/machine-id|g' ./usr/lib/libdbus-1.so.3 # or rm -f ./usr/lib/libdbus-1.so.3 || true # Remove python rm -f ./usr/bin/python* rm -f ./usr/bin/pydoc* rm -f ./usr/bin/pyenv* # remove big execs rm -f ./usr/bin/verify-uselistorder rm -f ./usr/bin/obj2yaml ./usr/bin/yaml2obj +rm -f ./usr/bin/kwrite ./usr/bin/kate cp /kdevelop.appdir/usr/lib/libexec/kf5/* /kdevelop.appdir/usr/bin/ cd / if [ ! -d appimage-exec-wrapper ]; then git clone git://anongit.kde.org/scratch/brauch/appimage-exec-wrapper fi; cd /appimage-exec-wrapper/ make clean make cd /kdevelop.appdir cp -v /appimage-exec-wrapper/exec.so exec_wrapper.so cat > AppRun << EOF #!/bin/bash DIR="\`dirname \"\$0\"\`" DIR="\`( cd \"\$DIR\" && pwd )\`" export APPDIR=\$DIR export LD_PRELOAD=\$DIR/exec_wrapper.so export APPIMAGE_ORIGINAL_QML2_IMPORT_PATH=\$QML2_IMPORT_PATH export APPIMAGE_ORIGINAL_LD_LIBRARY_PATH=\$LD_LIBRARY_PATH export APPIMAGE_ORIGINAL_QT_PLUGIN_PATH=\$QT_PLUGIN_PATH export APPIMAGE_ORIGINAL_XDG_DATA_DIRS=\$XDG_DATA_DIRS export APPIMAGE_ORIGINAL_PATH=\$PATH export APPIMAGE_ORIGINAL_PYTHONHOME=\$PYTHONHOME export QML2_IMPORT_PATH=\$DIR/usr/lib/qml:\$QML2_IMPORT_PATH export LD_LIBRARY_PATH=\$DIR/usr/lib/:\$LD_LIBRARY_PATH export QT_PLUGIN_PATH=\$DIR/usr/lib/qt5/plugins/ export XDG_DATA_DIRS=\$DIR/usr/share/:\$XDG_DATA_DIRS export PATH=\$DIR/usr/bin:\$PATH export KDE_FORK_SLAVES=1 export PYTHONHOME=\$DIR/usr/ export APPIMAGE_STARTUP_QML2_IMPORT_PATH=\$QML2_IMPORT_PATH export APPIMAGE_STARTUP_LD_LIBRARY_PATH=\$LD_LIBRARY_PATH export APPIMAGE_STARTUP_QT_PLUGIN_PATH=\$QT_PLUGIN_PATH export APPIMAGE_STARTUP_XDG_DATA_DIRS=\$XDG_DATA_DIRS export APPIMAGE_STARTUP_PATH=\$PATH export APPIMAGE_STARTUP_PYTHONHOME=\$PYTHONHOME export KDEV_DISABLE_PLUGINS=KDevWelcomePage cd \$HOME kdevelop \$@ EOF chmod +x AppRun cat > kdevelop.desktop << EOF [Desktop Entry] GenericName=Integrated development environment Name=KDevelop MimeType=text/plain; Exec=AppRun -b %U StartupNotify=true X-KDE-HasTempFileOption=true Icon=kdevelop X-DocPath=kdevelop/index.html Type=Application Terminal=false InitialPreference=9 Categories=Qt;KDE;Utility;TextEditor; EOF cp /kdevelop/app/icons/48-apps-kdevelop.png kdevelop.png cp -R /usr/lib/python3.6 /kdevelop.appdir/usr/lib/ rm -Rf /kdevelop.appdir/usr/lib/python3.6/{test,config-3.5m,__pycache__,site-packages,lib-dynload,distutils,idlelib,unittest,tkinter,ensurepip} mkdir -p /kdevelop.appdir/usr/share/kdevelop/ cp /kf5/build/breeze-icons/icons/breeze-icons.rcc /kdevelop.appdir/usr/share/kdevelop/icontheme.rcc rm -Rf /kdevelop.appdir/usr/share/icons/breeze* # not needed because of the rcc rm -f /kdevelop.appdir/usr/bin/llvm* rm -f /kdevelop.appdir/usr/bin/clang* rm -f /kdevelop.appdir/usr/bin/opt rm -f /kdevelop.appdir/usr/bin/lli rm -f /kdevelop.appdir/usr/bin/sancov rm -f /kdevelop.appdir/usr/bin/cmake rm -f /kdevelop.appdir/usr/bin/python rm -Rf /kdevelop.appdir/usr/lib/pkgconfig rm -Rf /kdevelop.appdir/usr/share/man rm -Rf /kdevelop.appdir/usr/share/locale rm -Rf /kdevelop.appdir/usr/lib/libLTO.so #At first it seems like "we shouldn't ship X11", but actually we should; the X11 protocol is sort of guaranteed to stay compatible, #while these libraries are not. # rm -Rf /kdevelop.appdir/usr/lib/libxcb* # add that back in # cp /usr/lib64/libxcb-keysyms.so.1 /kdevelop.appdir/usr/lib/ # rm -Rf /kdevelop.appdir/usr/lib/{libX11.so.6,libXau.so.6,libXext.so.6,libXi.so.6,libXxf86vm.so.1,libX11-xcb.so.1,libXdamage.so.1,libXfixes.so.3,libXrender.so.1} rm -f /kdevelop.appdir/usr/bin/llc rm -f /kdevelop.appdir/usr/bin/bugpoint find /kdevelop.appdir -name '*.a' -exec rm {} \; cd / APP=KDevelop VERSION="git" if [[ "$ARCH" = "x86_64" ]] ; then APPIMAGE=$APP"-"$VERSION"-x86_64.AppImage" fi if [[ "$ARCH" = "i686" ]] ; then APPIMAGE=$APP"-"$VERSION"-i386.AppImage" fi echo $APPIMAGE mkdir -p /out rm -f /out/*.AppImage || true AppImageKit/AppImageAssistant.AppDir/package /kdevelop.appdir/ /out/$APPIMAGE chmod a+rwx /out/$APPIMAGE # So that we can edit the AppImage outside of the Docker container diff --git a/documentation/qthelp/qthelpconfig.cpp b/documentation/qthelp/qthelpconfig.cpp index d947daa12f..a5b71c5b83 100644 --- a/documentation/qthelp/qthelpconfig.cpp +++ b/documentation/qthelp/qthelpconfig.cpp @@ -1,355 +1,355 @@ /* This file is part of KDevelop Copyright 2010 Benjamin Port Copyright 2014 Kevin Funk Copyright 2016 Andreas Cord-Landwehr 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 "qthelpconfig.h" #include #include #include #include #include #include #include "ui_qthelpconfig.h" #include "ui_qthelpconfigeditdialog.h" #include "qthelp_config_shared.h" #include "debug.h" #include "qthelpplugin.h" enum Column { NameColumn, PathColumn, IconColumn, GhnsColumn, ConfigColumn }; class QtHelpConfigEditDialog : public QDialog, public Ui_QtHelpConfigEditDialog { public: explicit QtHelpConfigEditDialog(QTreeWidgetItem* modifiedItem, QtHelpConfig* parent = nullptr, Qt::WindowFlags f = nullptr) : QDialog(parent, f) , m_modifiedItem(modifiedItem) , m_config(parent) { setupUi(this); if (modifiedItem) { setWindowTitle(i18n("Modify Entry")); } else { setWindowTitle(i18n("Add New Entry")); } qchIcon->setIcon("qtlogo"); } bool checkQtHelpFile(); void accept() override; private: QTreeWidgetItem* m_modifiedItem; QtHelpConfig* m_config; }; bool QtHelpConfigEditDialog::checkQtHelpFile() { //verify if the file is valid and if there is a name if(qchName->text().isEmpty()){ KMessageBox::error(this, i18n("Name cannot be empty.")); return false; } return m_config->checkNamespace(qchRequester->text(), m_modifiedItem); } void QtHelpConfigEditDialog::accept() { if (!checkQtHelpFile()) return; QDialog::accept(); } QtHelpConfig::QtHelpConfig(QtHelpPlugin* plugin, QWidget *parent) : KDevelop::ConfigPage(plugin, nullptr, parent) { m_configWidget = new Ui::QtHelpConfigUI; m_configWidget->setupUi(this); m_configWidget->addButton->setIcon(QIcon::fromTheme("list-add")); connect(m_configWidget->addButton, &QPushButton::clicked, this, &QtHelpConfig::add); // Table m_configWidget->qchTable->setColumnHidden(IconColumn, true); m_configWidget->qchTable->setColumnHidden(GhnsColumn, true); m_configWidget->qchTable->model()->setHeaderData(ConfigColumn, Qt::Horizontal, QVariant()); m_configWidget->qchTable->header()->setSectionsMovable(false); m_configWidget->qchTable->header()->setStretchLastSection(false); m_configWidget->qchTable->header()->setSectionResizeMode(NameColumn, QHeaderView::Stretch); m_configWidget->qchTable->header()->setSectionResizeMode(PathColumn, QHeaderView::Stretch); m_configWidget->qchTable->header()->setSectionResizeMode(ConfigColumn, QHeaderView::Fixed); // Add GHNS button KNS3::Button *knsButton = new KNS3::Button(i18nc("Allow user to get some API documentation with GHNS", "Get New Documentation"), "kdevelop-qthelp.knsrc", m_configWidget->boxQchManage); m_configWidget->tableCtrlLayout->insertWidget(1, knsButton); connect(knsButton, &KNS3::Button::dialogFinished, this, &QtHelpConfig::knsUpdate); connect(m_configWidget->loadQtDocsCheckBox, &QCheckBox::toggled, this, static_cast(&QtHelpConfig::changed)); m_configWidget->qchSearchDir->setMode(KFile::Directory); connect(m_configWidget->qchSearchDir, &KUrlRequester::textChanged, this, &QtHelpConfig::changed); // Set availability information for QtHelp m_configWidget->messageAvailabilityQtDocs->setCloseButtonVisible(false); if(plugin->isQtHelpAvailable()) { m_configWidget->messageAvailabilityQtDocs->setVisible(false); } else { m_configWidget->messageAvailabilityQtDocs->setText( i18n("The command \"qmake -query\" could not provide a path to a QtHelp file (QCH).")); m_configWidget->loadQtDocsCheckBox->setVisible(false); } reset(); } QtHelpConfig::~QtHelpConfig() { delete m_configWidget; } KDevelop::ConfigPage::ConfigPageType QtHelpConfig::configPageType() const { return KDevelop::ConfigPage::DocumentationConfigPage; } void QtHelpConfig::apply() { QStringList iconList, nameList, pathList, ghnsList; for (int i = 0; i < m_configWidget->qchTable->topLevelItemCount(); i++) { const QTreeWidgetItem* item = m_configWidget->qchTable->topLevelItem(i); nameList << item->text(0); pathList << item->text(1); iconList << item->text(2); ghnsList << item->text(3); } QString searchDir = m_configWidget->qchSearchDir->text(); bool loadQtDoc = m_configWidget->loadQtDocsCheckBox->isChecked(); qtHelpWriteConfig(iconList, nameList, pathList, ghnsList, searchDir, loadQtDoc); static_cast(plugin())->readConfig(); } void QtHelpConfig::reset() { m_configWidget->qchTable->clear(); QStringList iconList, nameList, pathList, ghnsList; QString searchDir; bool loadQtDoc; qtHelpReadConfig(iconList, nameList, pathList, ghnsList, searchDir, loadQtDoc); const int size = qMin(qMin(iconList.size(), nameList.size()), pathList.size()); for(int i = 0; i < size; ++i) { QString ghnsStatus = ghnsList.size()>i ? ghnsList.at(i) : "0"; addTableItem(iconList.at(i), nameList.at(i), pathList.at(i), ghnsStatus); } m_configWidget->qchSearchDir->setText(searchDir); m_configWidget->loadQtDocsCheckBox->setChecked(loadQtDoc); emit changed(); } void QtHelpConfig::defaults() { bool change = false; if(m_configWidget->qchTable->topLevelItemCount() > 0) { m_configWidget->qchTable->clear(); change = true; } if(!m_configWidget->loadQtDocsCheckBox->isChecked()){ m_configWidget->loadQtDocsCheckBox->setChecked(true); change = true; } if (change) { emit changed(); } } void QtHelpConfig::add() { QtHelpConfigEditDialog dialog(nullptr, this); if (!dialog.exec()) return; QTreeWidgetItem* item = addTableItem(dialog.qchIcon->icon(), dialog.qchName->text(), dialog.qchRequester->text(), "0"); m_configWidget->qchTable->setCurrentItem(item); emit changed(); } void QtHelpConfig::modify(QTreeWidgetItem* item) { if (!item) return; QtHelpConfigEditDialog dialog(item, this); if (item->text(GhnsColumn) != "0") { dialog.qchRequester->setText(i18n("Documentation provided by GHNS")); dialog.qchRequester->setEnabled(false); } else { dialog.qchRequester->setText(item->text(PathColumn)); dialog.qchRequester->setEnabled(true); } dialog.qchName->setText(item->text(NameColumn)); dialog.qchIcon->setIcon(item->text(IconColumn)); if (!dialog.exec()) { return; } item->setIcon(NameColumn, QIcon(dialog.qchIcon->icon())); item->setText(NameColumn, dialog.qchName->text()); item->setText(IconColumn, dialog.qchIcon->icon()); if(item->text(GhnsColumn) == "0") { item->setText(PathColumn, dialog.qchRequester->text()); } emit changed(); } bool QtHelpConfig::checkNamespace(const QString& filename, QTreeWidgetItem* modifiedItem) { QString qtHelpNamespace = QHelpEngineCore::namespaceName(filename); if (qtHelpNamespace.isEmpty()) { // Open error message (not valid Qt Compressed Help file) KMessageBox::error(this, i18n("Qt Compressed Help file is not valid.")); return false; } // verify if it's the namespace it's not already in the list for(int i=0; i < m_configWidget->qchTable->topLevelItemCount(); i++) { const QTreeWidgetItem* item = m_configWidget->qchTable->topLevelItem(i); if (item != modifiedItem){ if (qtHelpNamespace == QHelpEngineCore::namespaceName(item->text(PathColumn))) { // Open error message, documentation already imported KMessageBox::error(this, i18n("Documentation already imported")); return false; } } } return true; } void QtHelpConfig::remove(QTreeWidgetItem* item) { if (!item) return; delete item; emit changed(); } void QtHelpConfig::knsUpdate(KNS3::Entry::List list) { if (list.isEmpty()) return; foreach (const KNS3::Entry& e, list) { if(e.status() == KNS3::Entry::Installed) { if(e.installedFiles().size() == 1) { QString filename = e.installedFiles().at(0); if(checkNamespace(filename, nullptr)){ QTreeWidgetItem* item = addTableItem("documentation", e.name(), filename, "1"); m_configWidget->qchTable->setCurrentItem(item); } else { qCDebug(QTHELP) << "namespace error"; } } } else if(e.status() == KNS3::Entry::Deleted) { if(e.uninstalledFiles().size() == 1) { for(int i=0; i < m_configWidget->qchTable->topLevelItemCount(); i++) { QTreeWidgetItem* item = m_configWidget->qchTable->topLevelItem(i); if (e.uninstalledFiles().at(0) == item->text(PathColumn)) { delete item; break; } } } } } emit changed(); } QString QtHelpConfig::name() const { return i18n("Qt Help"); } QString QtHelpConfig::fullName() const { return i18n("Configure Qt Help Settings"); } QIcon QtHelpConfig::icon() const { return QIcon::fromTheme(QStringLiteral("qtlogo")); } QTreeWidgetItem * QtHelpConfig::addTableItem(const QString &icon, const QString &name, const QString &path, const QString &ghnsStatus) { QTreeWidgetItem *item = new QTreeWidgetItem(m_configWidget->qchTable); item->setIcon(NameColumn, QIcon::fromTheme(icon)); item->setText(NameColumn, name); item->setToolTip(NameColumn, name); item->setText(PathColumn, path); item->setToolTip(PathColumn, path); item->setText(IconColumn, icon); item->setText(GhnsColumn, ghnsStatus); QWidget *ctrlWidget = new QWidget(item->treeWidget()); ctrlWidget->setLayout(new QHBoxLayout(ctrlWidget)); QToolButton *modifyBtn = new QToolButton(item->treeWidget()); modifyBtn->setIcon(QIcon::fromTheme("document-edit")); modifyBtn->setToolTip(i18n("Modify")); connect(modifyBtn, &QPushButton::clicked, this, [=](){ modify(item); }); QToolButton *removeBtn = new QToolButton(item->treeWidget()); removeBtn->setIcon(QIcon::fromTheme("entry-delete")); removeBtn->setToolTip(i18n("Delete")); if (item->text(GhnsColumn) != "0") { // KNS3 currently does not provide API to uninstall entries // just removing the files results in wrong installed states in the KNS3 dialog // TODO: add API to KNS to remove files without UI interaction removeBtn->setEnabled(false); - removeBtn->setToolTip(tr("Please uninstall this via GHNS")); + removeBtn->setToolTip(i18n("Please uninstall this via GHNS")); } else { connect(removeBtn, &QPushButton::clicked, this, [=](){ remove(item); }); } ctrlWidget->layout()->addWidget(modifyBtn); ctrlWidget->layout()->addWidget(removeBtn); m_configWidget->qchTable->setItemWidget(item, ConfigColumn, ctrlWidget); return item; } diff --git a/languages/clang/duchain/clangproblem.cpp b/languages/clang/duchain/clangproblem.cpp index 7afbb50472..cf1c915029 100644 --- a/languages/clang/duchain/clangproblem.cpp +++ b/languages/clang/duchain/clangproblem.cpp @@ -1,268 +1,268 @@ /* * Copyright 2014 Kevin Funk * * 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 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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 . * */ #include "clangproblem.h" #include #include #include "util/clangtypes.h" #include "util/clangdebug.h" #include #include #include using namespace KDevelop; namespace { IProblem::Severity diagnosticSeverityToSeverity(CXDiagnosticSeverity severity, const QString& optionName) { switch (severity) { case CXDiagnostic_Fatal: case CXDiagnostic_Error: return IProblem::Error; case CXDiagnostic_Warning: if (optionName.startsWith(QLatin1String("-Wunused-"))) { return IProblem::Hint; } return IProblem::Warning; break; default: return IProblem::Hint; } } /** * Clang diagnostic messages always start with a lowercase character * * @return Prettified version, starting with uppercase character */ inline QString prettyDiagnosticSpelling(const QString& str) { QString ret = str; if (ret.isEmpty()) { return {}; } ret[0] = ret[0].toUpper(); return ret; } ClangFixits fixitsForDiagnostic(CXDiagnostic diagnostic) { ClangFixits fixits; auto numFixits = clang_getDiagnosticNumFixIts(diagnostic); for (uint i = 0; i < numFixits; ++i) { CXSourceRange range; const QString replacementText = ClangString(clang_getDiagnosticFixIt(diagnostic, i, &range)).toString(); auto location = ClangString(clang_formatDiagnostic(diagnostic, CXDiagnostic_DisplaySourceLocation)).toString(); const auto docRange = ClangRange(range).toDocumentRange(); auto doc = KDevelop::ICore::self()->documentController()->documentForUrl(docRange.document.toUrl()); const QString original = doc ? doc->text(docRange) : QString{}; fixits << ClangFixit{replacementText, docRange, QString(), original}; } return fixits; } } QDebug operator<<(QDebug debug, const ClangFixit& fixit) { debug.nospace() << "ClangFixit[" << "replacementText=" << fixit.replacementText << ", range=" << fixit.range << ", description=" << fixit.description << "]"; return debug; } ClangProblem::ClangProblem(CXDiagnostic diagnostic, CXTranslationUnit unit) { const QString diagnosticOption = ClangString(clang_getDiagnosticOption(diagnostic, nullptr)).toString(); auto severity = diagnosticSeverityToSeverity(clang_getDiagnosticSeverity(diagnostic), diagnosticOption); setSeverity(severity); QString description = ClangString(clang_getDiagnosticSpelling(diagnostic)).toString(); if (!diagnosticOption.isEmpty()) { description.append(QStringLiteral(" [%1]").arg(diagnosticOption)); } setDescription(prettyDiagnosticSpelling(description)); ClangLocation location(clang_getDiagnosticLocation(diagnostic)); CXFile diagnosticFile; clang_getFileLocation(location, &diagnosticFile, nullptr, nullptr, nullptr); const ClangString fileName(clang_getFileName(diagnosticFile)); DocumentRange docRange(IndexedString(QUrl::fromLocalFile(fileName.toString()).adjusted(QUrl::NormalizePathSegments)), KTextEditor::Range(location, location)); const uint numRanges = clang_getDiagnosticNumRanges(diagnostic); for (uint i = 0; i < numRanges; ++i) { auto range = ClangRange(clang_getDiagnosticRange(diagnostic, i)).toRange(); if(!range.isValid()){ continue; } if (range.start() < docRange.start()) { docRange.setStart(range.start()); } if (range.end() > docRange.end()) { docRange.setEnd(range.end()); } } if (docRange.isEmpty()) { // try to find a bigger range for the given location by using the token at the given location CXFile file = nullptr; unsigned line = 0; unsigned column = 0; clang_getExpansionLocation(location, &file, &line, &column, nullptr); // just skip ahead some characters, hoping that it's sufficient to encompass // a token we can use for building the range auto nextLocation = clang_getLocation(unit, file, line, column + 100); auto rangeToTokenize = clang_getRange(location, nextLocation); const ClangTokens tokens(unit, rangeToTokenize); if (tokens.size()) { docRange.setRange(ClangRange(clang_getTokenExtent(unit, tokens.at(0))).toRange()); } } setFixits(fixitsForDiagnostic(diagnostic)); setFinalLocation(docRange); setSource(IProblem::SemanticAnalysis); QVector diagnostics; auto childDiagnostics = clang_getChildDiagnostics(diagnostic); auto numChildDiagnostics = clang_getNumDiagnosticsInSet(childDiagnostics); for (uint j = 0; j < numChildDiagnostics; ++j) { auto childDiagnostic = clang_getDiagnosticInSet(childDiagnostics, j); ClangProblem::Ptr problem(new ClangProblem(childDiagnostic, unit)); diagnostics << ProblemPointer(problem.data()); } setDiagnostics(diagnostics); } IAssistant::Ptr ClangProblem::solutionAssistant() const { if (allFixits().isEmpty()) { return {}; } return IAssistant::Ptr(new ClangFixitAssistant(allFixits())); } ClangFixits ClangProblem::fixits() const { return m_fixits; } void ClangProblem::setFixits(const ClangFixits& fixits) { m_fixits = fixits; } ClangFixits ClangProblem::allFixits() const { ClangFixits result; result << m_fixits; for (const IProblem::Ptr& diagnostic : diagnostics()) { const Ptr problem(dynamic_cast(diagnostic.data())); Q_ASSERT(problem); result << problem->allFixits(); } return result; } ClangFixitAssistant::ClangFixitAssistant(const ClangFixits& fixits) - : m_title(tr("Fix-it Hints")) + : m_title(i18n("Fix-it Hints")) , m_fixits(fixits) { } ClangFixitAssistant::ClangFixitAssistant(const QString& title, const ClangFixits& fixits) : m_title(title) , m_fixits(fixits) { } QString ClangFixitAssistant::title() const { return m_title; } void ClangFixitAssistant::createActions() { KDevelop::IAssistant::createActions(); for (const ClangFixit& fixit : m_fixits) { addAction(IAssistantAction::Ptr(new ClangFixitAction(fixit))); } } ClangFixits ClangFixitAssistant::fixits() const { return m_fixits; } ClangFixitAction::ClangFixitAction(const ClangFixit& fixit) : m_fixit(fixit) { } QString ClangFixitAction::description() const { if (!m_fixit.description.isEmpty()) return m_fixit.description; const auto range = m_fixit.range; if (range.start() == range.end()) { return i18n("Insert \"%1\" at line: %2, column: %3", m_fixit.replacementText, range.start().line()+1, range.start().column()+1); } else if (range.start().line() == range.end().line()) { if (m_fixit.currentText.isEmpty()) { return i18n("Replace text at line: %1, column: %2 with: \"%3\"", range.start().line()+1, range.start().column()+1, m_fixit.replacementText); } else return i18n("Replace \"%1\" with: \"%2\"", m_fixit.currentText, m_fixit.replacementText); } else { return i18n("Replace multiple lines starting at line: %1, column: %2 with: \"%3\"", range.start().line()+1, range.start().column()+1, m_fixit.replacementText); } } void ClangFixitAction::execute() { DocumentChangeSet changes; { DUChainReadLocker lock; DocumentChange change(m_fixit.range.document, m_fixit.range, m_fixit.currentText, m_fixit.replacementText); change.m_ignoreOldText = !m_fixit.currentText.isEmpty(); changes.addChange(change); } changes.setReplacementPolicy(DocumentChangeSet::WarnOnFailedChange); changes.applyAllChanges(); emit executed(this); } diff --git a/languages/clang/duchain/unknowndeclarationproblem.cpp b/languages/clang/duchain/unknowndeclarationproblem.cpp index 493edda4c4..29b2aef3f7 100644 --- a/languages/clang/duchain/unknowndeclarationproblem.cpp +++ b/languages/clang/duchain/unknowndeclarationproblem.cpp @@ -1,555 +1,557 @@ /* * Copyright 2014 Jørgen Kvalsvik * Copyright 2014 Kevin Funk * * 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 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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 . * */ #include "unknowndeclarationproblem.h" #include "clanghelpers.h" #include "parsesession.h" #include "../util/clangdebug.h" #include "../util/clangutils.h" #include "../util/clangtypes.h" #include "../clangsettings/clangsettingsmanager.h" #include #include #include #include #include #include #include #include #include #include #include #include #include +#include + #include #include #include using namespace KDevelop; namespace { /** Under some conditions, such as when looking up suggestions * for the undeclared namespace 'std' we will get an awful lot * of suggestions. This parameter limits how many suggestions * will pop up, as rarely more than a few will be relevant anyways * * Forward declaration suggestions are included in this number */ const int maxSuggestions = 5; /** * We don't want anything from the bits directory - * we'd rather prefer forwarding includes, such as */ bool isBlacklisted(const QString& path) { if (ClangHelpers::isSource(path)) return true; // Do not allow including directly from the bits directory. // Instead use one of the forwarding headers in other directories, when possible. if (path.contains( QLatin1String("bits") ) && path.contains(QLatin1String("/include/c++/"))) return true; return false; } QStringList scanIncludePaths( const QString& identifier, const QDir& dir, int maxDepth = 3 ) { if (!maxDepth) { return {}; } QStringList candidates; const auto path = dir.absolutePath(); if( isBlacklisted( path ) ) { return {}; } const QStringList nameFilters = {identifier, identifier + QLatin1String(".*")}; for (const auto& file : dir.entryList(nameFilters, QDir::Files)) { if (identifier.compare(file, Qt::CaseInsensitive) == 0 || ClangHelpers::isHeader(file)) { const QString filePath = path + QLatin1Char('/') + file; clangDebug() << "Found candidate file" << filePath; candidates.append( filePath ); } } maxDepth--; for( const auto& subdir : dir.entryList( QDir::Dirs | QDir::NoDotAndDotDot ) ) candidates += scanIncludePaths( identifier, QDir{ path + QLatin1Char('/') + subdir }, maxDepth ); return candidates; } /** * Find files in dir that match the given identifier. Matches common C++ header file extensions only. */ QStringList scanIncludePaths( const QualifiedIdentifier& identifier, const KDevelop::Path::List& includes ) { const auto stripped_identifier = identifier.last().toString(); QStringList candidates; for( const auto& include : includes ) { candidates += scanIncludePaths( stripped_identifier, QDir{ include.toLocalFile() } ); } std::sort( candidates.begin(), candidates.end() ); candidates.erase( std::unique( candidates.begin(), candidates.end() ), candidates.end() ); return candidates; } /** * Determine how much path is shared between two includes. * boost/tr1/unordered_map * boost/tr1/unordered_set * have a shared path of 2 where * boost/tr1/unordered_map * boost/vector * have a shared path of 1 */ int sharedPathLevel(const QString& a, const QString& b) { int shared = -1; for(auto x = a.begin(), y = b.begin(); *x == *y && x != a.end() && y != b.end() ; ++x, ++y ) { if( *x == QDir::separator() ) { ++shared; } } return shared; } /** * Try to find a proper include position from the DUChain: * * look at existing imports (i.e. #include's) and find a fitting * file with the same/similar path to the new include file and use that * * TODO: Implement a fallback scheme */ KDevelop::DocumentRange includeDirectivePosition(const KDevelop::Path& source, const QString& includeFile) { static const QRegularExpression mocFilenameExpression(QStringLiteral("(moc_[^\\/\\\\]+\\.cpp$|\\.moc$)") ); DUChainReadLocker lock; const TopDUContext* top = DUChainUtils::standardContextForUrl( source.toUrl() ); if( !top ) { clangDebug() << "unable to find standard context for" << source.toLocalFile() << "Creating null range"; return KDevelop::DocumentRange::invalid(); } int line = -1; // look at existing #include statements and re-use them int currentMatchQuality = -1; for( const auto& import : top->importedParentContexts() ) { const auto importFilename = import.context(top)->url().str(); const int matchQuality = sharedPathLevel( importFilename , includeFile ); if( matchQuality < currentMatchQuality ) { continue; } const auto match = mocFilenameExpression.match(importFilename); if (match.isValid()) { clangDebug() << "moc file detected in" << source.toUrl().toDisplayString() << ":" << importFilename << "-- not using as include insertion location"; continue; } line = import.position.line + 1; currentMatchQuality = matchQuality; } if( line == -1 ) { /* Insert at the top of the document */ return {IndexedString(source.pathOrUrl()), {0, 0, 0, 0}}; } return {IndexedString(source.pathOrUrl()), {line, 0, line, 0}}; } KDevelop::DocumentRange forwardDeclarationPosition(const QualifiedIdentifier& identifier, const KDevelop::Path& source) { DUChainReadLocker lock; const TopDUContext* top = DUChainUtils::standardContextForUrl( source.toUrl() ); if( !top ) { clangDebug() << "unable to find standard context for" << source.toLocalFile() << "Creating null range"; return KDevelop::DocumentRange::invalid(); } if (!top->findDeclarations(identifier).isEmpty()) { // Already forward-declared return KDevelop::DocumentRange::invalid(); } int line = std::numeric_limits< int >::max(); for( const auto decl : top->localDeclarations() ) { line = std::min( line, decl->range().start.line ); } if( line == std::numeric_limits< int >::max() ) { return KDevelop::DocumentRange::invalid(); } // We want it one line above the first declaration line = std::max( line - 1, 0 ); return {IndexedString(source.pathOrUrl()), {line, 0, line, 0}}; } /** * Iteratively build all levels of the current scope. A (missing) type anywhere * can be arbitrarily namespaced, so we create the permutations of possible * nestings of namespaces it can currently be in, * * TODO: add detection of namespace aliases, such as 'using namespace KDevelop;' * * namespace foo { * namespace bar { * function baz() { * type var; * } * } * } * * Would give: * foo::bar::baz::type * foo::bar::type * foo::type * type */ QVector findPossibleQualifiedIdentifiers( const QualifiedIdentifier& identifier, const KDevelop::Path& file, const KDevelop::CursorInRevision& cursor ) { DUChainReadLocker lock; const TopDUContext* top = DUChainUtils::standardContextForUrl( file.toUrl() ); if( !top ) { clangDebug() << "unable to find standard context for" << file.toLocalFile() << "Not creating duchain candidates"; return {}; } const auto* context = top->findContextAt( cursor ); if( !context ) { clangDebug() << "No context found at" << cursor; return {}; } QVector declarations{ identifier }; for( auto scopes = context->scopeIdentifier(); !scopes.isEmpty(); scopes.pop() ) { declarations.append( scopes + identifier ); } clangDebug() << "Possible declarations:" << declarations; return declarations; } } QStringList UnknownDeclarationProblem::findMatchingIncludeFiles(const QVector& declarations) { DUChainReadLocker lock; QStringList candidates; for (const auto decl: declarations) { // skip declarations that don't belong to us const auto& file = decl->topContext()->parsingEnvironmentFile(); if (!file || file->language() != ParseSession::languageString()) { continue; } if( dynamic_cast( decl ) ) { continue; } if( decl->isForwardDeclaration() ) { continue; } const auto filepath = decl->url().toUrl().toLocalFile(); if( !isBlacklisted( filepath ) ) { candidates << filepath; clangDebug() << "Adding" << filepath << "determined from candidate" << decl->toString(); } for( const auto importer : file->importers() ) { if( importer->imports().count() != 1 && !isBlacklisted( filepath ) ) { continue; } if( importer->topContext()->localDeclarations().count() ) { continue; } const auto filePath = importer->url().toUrl().toLocalFile(); if( isBlacklisted( filePath ) ) { continue; } /* This file is a forwarder, such as * does not actually implement the functions, but include other headers that do * we prefer this to other headers */ candidates << filePath; clangDebug() << "Adding forwarder file" << filePath << "to the result set"; } } std::sort( candidates.begin(), candidates.end() ); candidates.erase( std::unique( candidates.begin(), candidates.end() ), candidates.end() ); clangDebug() << "Candidates: " << candidates; return candidates; } namespace { /** * Takes a filepath and the include paths and determines what directive to use. */ ClangFixit directiveForFile( const QString& includefile, const KDevelop::Path::List& includepaths, const KDevelop::Path& source ) { const auto sourceFolder = source.parent(); const Path canonicalFile( QFileInfo( includefile ).canonicalFilePath() ); QString shortestDirective; bool isRelative = false; // we can include the file directly if (sourceFolder == canonicalFile.parent()) { shortestDirective = canonicalFile.lastPathSegment(); isRelative = true; } else { // find the include directive with the shortest length for( const auto& includePath : includepaths ) { QString relative = includePath.relativePath( canonicalFile ); if( relative.startsWith( QLatin1String("./") ) ) relative = relative.mid( 2 ); if( shortestDirective.isEmpty() || relative.length() < shortestDirective.length() ) { shortestDirective = relative; isRelative = includePath == sourceFolder; } } } if( shortestDirective.isEmpty() ) { // Item not found in include path return {}; } const auto range = DocumentRange(IndexedString(source.pathOrUrl()), includeDirectivePosition(source, canonicalFile.lastPathSegment())); if( !range.isValid() ) { clangDebug() << "unable to determine valid position for" << includefile << "in" << source.pathOrUrl(); return {}; } QString directive; if( isRelative ) { directive = QStringLiteral("#include \"%1\"").arg(shortestDirective); } else { directive = QStringLiteral("#include <%1>").arg(shortestDirective); } - return ClangFixit{directive + QLatin1Char('\n'), range, QObject::tr("Insert \'%1\'").arg(directive)}; + return ClangFixit{directive + QLatin1Char('\n'), range, i18n("Insert \'%1\'", directive)}; } KDevelop::Path::List includePaths( const KDevelop::Path& file ) { // Find project's custom include paths const auto source = file.toLocalFile(); const auto item = ICore::self()->projectController()->projectModel()->itemForPath( KDevelop::IndexedString( source ) ); return IDefinesAndIncludesManager::manager()->includes(item); } /** * Return a list of header files viable for inclusions. All elements will be unique */ QStringList includeFiles(const QualifiedIdentifier& identifier, const QVector& declarations, const KDevelop::Path& file) { const auto includes = includePaths( file ); if( includes.isEmpty() ) { clangDebug() << "Include path is empty"; return {}; } const auto candidates = UnknownDeclarationProblem::findMatchingIncludeFiles(declarations); if( !candidates.isEmpty() ) { // If we find a candidate from the duchain we don't bother scanning the include paths return candidates; } return scanIncludePaths(identifier, includes); } /** * Construct viable forward declarations for the type name. */ ClangFixits forwardDeclarations(const QVector& matchingDeclarations, const Path& source) { DUChainReadLocker lock; ClangFixits fixits; for (const auto decl : matchingDeclarations) { const auto qid = decl->qualifiedIdentifier(); if (qid.count() > 1) { // TODO: Currently we're not able to determine what is namespaces, class names etc // and makes a suitable forward declaration, so just suggest "vanilla" declarations. continue; } const auto range = forwardDeclarationPosition(qid, source); if (!range.isValid()) { continue; // do not know where to insert } if (const auto classDecl = dynamic_cast(decl)) { const auto name = qid.last().toString(); switch (classDecl->classType()) { case ClassDeclarationData::Class: fixits += { QLatin1String("class ") + name + QLatin1String(";\n"), range, - QObject::tr("Forward declare as 'class'") + i18n("Forward declare as 'class'") }; break; case ClassDeclarationData::Struct: fixits += { QLatin1String("struct ") + name + QLatin1String(";\n"), range, - QObject::tr("Forward declare as 'struct'") + i18n("Forward declare as 'struct'") }; break; default: break; } } } return fixits; } /** * Search the persistent symbol table for matching declarations for identifiers @p identifiers */ QVector findMatchingDeclarations(const QVector& identifiers) { DUChainReadLocker lock; QVector matchingDeclarations; matchingDeclarations.reserve(identifiers.size()); for (const auto& declaration : identifiers) { clangDebug() << "Considering candidate declaration" << declaration; const IndexedDeclaration* declarations; uint declarationCount; PersistentSymbolTable::self().declarations( declaration , declarationCount, declarations ); for (uint i = 0; i < declarationCount; ++i) { // Skip if the declaration is invalid or if it is an alias declaration - // we want the actual declaration (and its file) if (auto decl = declarations[i].declaration()) { matchingDeclarations << decl; } } } return matchingDeclarations; } ClangFixits fixUnknownDeclaration( const QualifiedIdentifier& identifier, const KDevelop::Path& file, const KDevelop::DocumentRange& docrange ) { ClangFixits fixits; const CursorInRevision cursor{docrange.start().line(), docrange.start().column()}; const auto possibleIdentifiers = findPossibleQualifiedIdentifiers(identifier, file, cursor); const auto matchingDeclarations = findMatchingDeclarations(possibleIdentifiers); if (ClangSettingsManager::self()->assistantsSettings().forwardDeclare) { for (const auto& fixit : forwardDeclarations(matchingDeclarations, file)) { fixits << fixit; if (fixits.size() == maxSuggestions) { return fixits; } } } const auto includefiles = includeFiles(identifier, matchingDeclarations, file); if (includefiles.isEmpty()) { // return early as the computation of the include paths is quite expensive return fixits; } const auto includepaths = includePaths( file ); clangDebug() << "found include paths for" << file << ":" << includepaths; /* create fixits for candidates */ for( const auto& includeFile : includefiles ) { const auto fixit = directiveForFile( includeFile, includepaths, file /* UP */ ); if (!fixit.range.isValid()) { clangDebug() << "unable to create directive for" << includeFile << "in" << file.toLocalFile(); continue; } fixits << fixit; if (fixits.size() == maxSuggestions) { return fixits; } } return fixits; } QString symbolFromDiagnosticSpelling(const QString& str) { /* in all error messages the symbol is in in the first pair of quotes */ const auto split = str.split( QLatin1Char('\'') ); auto symbol = split.value( 1 ); if( str.startsWith( QLatin1String("No member named") ) ) { symbol = split.value( 3 ) + QLatin1String("::") + split.value( 1 ); } return symbol; } } UnknownDeclarationProblem::UnknownDeclarationProblem(CXDiagnostic diagnostic, CXTranslationUnit unit) : ClangProblem(diagnostic, unit) { setSymbol(QualifiedIdentifier(symbolFromDiagnosticSpelling(description()))); } void UnknownDeclarationProblem::setSymbol(const QualifiedIdentifier& identifier) { m_identifier = identifier; } IAssistant::Ptr UnknownDeclarationProblem::solutionAssistant() const { const Path path(finalLocation().document.str()); const auto fixits = allFixits() + fixUnknownDeclaration(m_identifier, path, finalLocation()); return IAssistant::Ptr(new ClangFixitAssistant(fixits)); } diff --git a/languages/clang/tests/test_assistants.cpp b/languages/clang/tests/test_assistants.cpp index fcf3c6a93f..09b1db8154 100644 --- a/languages/clang/tests/test_assistants.cpp +++ b/languages/clang/tests/test_assistants.cpp @@ -1,792 +1,794 @@ /* This file is part of KDevelop Copyright 2012 Olivier de Gaalon 2014 David Stevens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. 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 "test_assistants.h" #include "codegen/clangrefactoring.h" #include #include #include +#include + #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; using namespace KTextEditor; QTEST_MAIN(TestAssistants) ForegroundLock *globalTestLock = nullptr; StaticAssistantsManager *staticAssistantsManager() { return Core::self()->languageController()->staticAssistantsManager(); } void TestAssistants::initTestCase() { QLoggingCategory::setFilterRules(QStringLiteral( "*.debug=false\n" "default.debug=true\n" "kdevelop.plugins.clang.debug=true\n" )); QVERIFY(qputenv("KDEV_DISABLE_PLUGINS", "kdevcppsupport")); QVERIFY(qputenv("KDEV_CLANG_DISPLAY_DIAGS", "1")); AutoTestShell::init({QStringLiteral("kdevclangsupport"), QStringLiteral("kdevproblemreporter")}); TestCore::initialize(); DUChain::self()->disablePersistentStorage(); Core::self()->languageController()->backgroundParser()->setDelay(0); Core::self()->sourceFormatterController()->disableSourceFormatting(true); CodeRepresentation::setDiskChangesForbidden(true); globalTestLock = new ForegroundLock; } void TestAssistants::cleanupTestCase() { Core::self()->cleanup(); delete globalTestLock; globalTestLock = nullptr; } static QUrl createFile(const QString& fileContents, const QString& extension, int id) { static QTemporaryDir tempDirA; Q_ASSERT(tempDirA.isValid()); static QDir dirA(tempDirA.path()); QFile file(dirA.filePath(QString::number(id) + extension)); file.open(QIODevice::WriteOnly | QIODevice::Text); file.write(fileContents.toUtf8()); file.close(); return QUrl::fromLocalFile(file.fileName()); } class Testbed { public: enum TestDoc { HeaderDoc, CppDoc }; enum IncludeBehavior { NoAutoInclude, AutoInclude, }; Testbed(const QString& headerContents, const QString& cppContents, IncludeBehavior include = AutoInclude) : m_includeBehavior(include) { static int i = 0; int id = i; ++i; m_headerDocument.url = createFile(headerContents,".h",id); m_headerDocument.textDoc = openDocument(m_headerDocument.url); QString preamble; if (include == AutoInclude) preamble = QStringLiteral("#include \"%1\"\n").arg(m_headerDocument.url.toLocalFile()); m_cppDocument.url = createFile(preamble + cppContents,".cpp",id); m_cppDocument.textDoc = openDocument(m_cppDocument.url); } ~Testbed() { Core::self()->documentController()->documentForUrl(m_cppDocument.url)->textDocument(); Core::self()->documentController()->documentForUrl(m_cppDocument.url)->close(KDevelop::IDocument::Discard); Core::self()->documentController()->documentForUrl(m_headerDocument.url)->close(KDevelop::IDocument::Discard); } void changeDocument(TestDoc which, Range where, const QString& what, bool waitForUpdate = false) { TestDocument document; if (which == CppDoc) { document = m_cppDocument; if (m_includeBehavior == AutoInclude) { where = Range(where.start().line() + 1, where.start().column(), where.end().line() + 1, where.end().column()); //The include adds a line } } else { document = m_headerDocument; } // we must activate the document, otherwise we cannot find the correct active view auto kdevdoc = ICore::self()->documentController()->documentForUrl(document.url); QVERIFY(kdevdoc); ICore::self()->documentController()->activateDocument(kdevdoc); auto view = ICore::self()->documentController()->activeTextDocumentView(); QCOMPARE(view->document(), document.textDoc); view->setSelection(where); view->removeSelectionText(); view->setCursorPosition(where.start()); view->insertText(what); QCoreApplication::processEvents(); if (waitForUpdate) { DUChain::self()->waitForUpdate(IndexedString(document.url), KDevelop::TopDUContext::AllDeclarationsAndContexts); } } QString documentText(TestDoc which) { if (which == CppDoc) { //The CPP document text shouldn't include the autogenerated include line QString text = m_cppDocument.textDoc->text(); return m_includeBehavior == AutoInclude ? text.mid(text.indexOf("\n") + 1) : text; } else return m_headerDocument.textDoc->text(); } QString includeFileName() const { return m_headerDocument.url.toLocalFile(); } KTextEditor::Document *document(TestDoc which) const { return Core::self()->documentController()->documentForUrl( which == CppDoc ? m_cppDocument.url : m_headerDocument.url)->textDocument(); } private: struct TestDocument { QUrl url; Document *textDoc; }; Document* openDocument(const QUrl& url) { Core::self()->documentController()->openDocument(url); DUChain::self()->waitForUpdate(IndexedString(url), KDevelop::TopDUContext::AllDeclarationsAndContexts); return Core::self()->documentController()->documentForUrl(url)->textDocument(); } IncludeBehavior m_includeBehavior; TestDocument m_headerDocument; TestDocument m_cppDocument; }; /** * A StateChange describes an insertion/deletion/replacement and the expected result **/ struct StateChange { StateChange(){}; StateChange(Testbed::TestDoc document, const Range& range, const QString& newText, const QString& result) : document(document) , range(range) , newText(newText) , result(result) { } Testbed::TestDoc document; Range range; QString newText; QString result; }; Q_DECLARE_METATYPE(StateChange) Q_DECLARE_METATYPE(QList) void TestAssistants::testRenameAssistant_data() { QTest::addColumn("fileContents"); QTest::addColumn("oldDeclarationName"); QTest::addColumn >("stateChanges"); QTest::addColumn("finalFileContents"); QTest::newRow("Prepend Text") << "int foo(int i)\n { i = 0; return i; }" << "i" << QList{ StateChange(Testbed::CppDoc, Range(0,12,0,12), "u", "ui"), StateChange(Testbed::CppDoc, Range(0,13,0,13), "z", "uzi"), } << "int foo(int uzi)\n { uzi = 0; return uzi; }"; QTest::newRow("Append Text") << "int foo(int i)\n { i = 0; return i; }" << "i" << (QList() << StateChange(Testbed::CppDoc, Range(0,13,0,13), "d", "id")) << "int foo(int id)\n { id = 0; return id; }"; QTest::newRow("Replace Text") << "int foo(int i)\n { i = 0; return i; }" << "i" << (QList() << StateChange(Testbed::CppDoc, Range(0,12,0,13), "u", "u")) << "int foo(int u)\n { u = 0; return u; }"; QTest::newRow("Paste Replace") << "int foo(int abg)\n { abg = 0; return abg; }" << "abg" << (QList() << StateChange(Testbed::CppDoc, Range(0,12,0,15), "abcdefg", "abcdefg")) << "int foo(int abcdefg)\n { abcdefg = 0; return abcdefg; }"; QTest::newRow("Paste Insert") << "int foo(int abg)\n { abg = 0; return abg; }" << "abg" << (QList() << StateChange(Testbed::CppDoc, Range(0,14,0,14), "cdef", "abcdefg")) << "int foo(int abcdefg)\n { abcdefg = 0; return abcdefg; }"; QTest::newRow("Letter-by-Letter Prepend") << "int foo(int i)\n { i = 0; return i; }" << "i" << (QList() << StateChange(Testbed::CppDoc, Range(0,12,0,12), "a", "ai") << StateChange(Testbed::CppDoc, Range(0,13,0,13), "b", "abi") << StateChange(Testbed::CppDoc, Range(0,14,0,14), "c", "abci") ) << "int foo(int abci)\n { abci = 0; return abci; }"; QTest::newRow("Letter-by-Letter Insert") << "int foo(int abg)\n { abg = 0; return abg; }" << "abg" << (QList() << StateChange(Testbed::CppDoc, Range(0,14,0,14), "c", "abcg") << StateChange(Testbed::CppDoc, Range(0,15,0,15), "d", "abcdg") << StateChange(Testbed::CppDoc, Range(0,16,0,16), "e", "abcdeg") << StateChange(Testbed::CppDoc, Range(0,17,0,17), "f", "abcdefg") ) << "int foo(int abcdefg)\n { abcdefg = 0; return abcdefg; }"; } ProblemPointer findStaticAssistantProblem(const QVector& problems) { const auto renameProblemIt = std::find_if(problems.cbegin(), problems.cend(), [](const ProblemPointer& p) { return dynamic_cast(p.constData()); }); if (renameProblemIt != problems.cend()) return *renameProblemIt; return {}; } void TestAssistants::testRenameAssistant() { QFETCH(QString, fileContents); Testbed testbed("", fileContents); const auto document = testbed.document(Testbed::CppDoc); QVERIFY(document); QExplicitlySharedDataPointer assistant; QFETCH(QString, oldDeclarationName); QFETCH(QList, stateChanges); foreach(StateChange stateChange, stateChanges) { testbed.changeDocument(Testbed::CppDoc, stateChange.range, stateChange.newText, true); DUChainReadLocker lock; auto topCtx = DUChain::self()->chainForDocument(document->url()); QVERIFY(topCtx); const auto problem = findStaticAssistantProblem(DUChainUtils::allProblemsForContext(topCtx)); if (problem) assistant = problem->solutionAssistant(); if (stateChange.result.isEmpty()) { QVERIFY(!assistant || !assistant->actions().size()); } else { qWarning() << assistant.data() << stateChange.result; QVERIFY(assistant && assistant->actions().size()); RenameAction *r = qobject_cast(assistant->actions().first().data()); QCOMPARE(r->oldDeclarationName(), oldDeclarationName); QCOMPARE(r->newDeclarationName(), stateChange.result); } } if (assistant && assistant->actions().size()) { assistant->actions().first()->execute(); } QFETCH(QString, finalFileContents); QCOMPARE(testbed.documentText(Testbed::CppDoc), finalFileContents); } void TestAssistants::testRenameAssistantUndoRename() { Testbed testbed("", "int foo(int i)\n { i = 0; return i; }"); testbed.changeDocument(Testbed::CppDoc, Range(0,13,0,13), "d", true); const auto document = testbed.document(Testbed::CppDoc); QVERIFY(document); DUChainReadLocker lock; auto topCtx = DUChain::self()->chainForDocument(document->url()); QVERIFY(topCtx); auto firstProblem = findStaticAssistantProblem(DUChainUtils::allProblemsForContext(topCtx)); QVERIFY(firstProblem); auto assistant = firstProblem->solutionAssistant(); QVERIFY(assistant); QVERIFY(assistant->actions().size() > 0); RenameAction *r = qobject_cast(assistant->actions().first().data()); qWarning() << topCtx->problems() << assistant->actions().first().data() << assistant->actions().size(); QVERIFY(r); // now rename the variable back to its original identifier testbed.changeDocument(Testbed::CppDoc, Range(0,13,0,14), ""); // there should be no assistant anymore QVERIFY(!assistant || assistant->actions().isEmpty()); } const QString SHOULD_ASSIST = "SHOULD_ASSIST"; //An assistant will be visible const QString NO_ASSIST = "NO_ASSIST"; //No assistant visible void TestAssistants::testSignatureAssistant_data() { QTest::addColumn("headerContents"); QTest::addColumn("cppContents"); QTest::addColumn >("stateChanges"); QTest::addColumn("finalHeaderContents"); QTest::addColumn("finalCppContents"); QTest::newRow("change_argument_type") << "class Foo {\nint bar(int a, char* b, int c = 10); \n};" << "int Foo::bar(int a, char* b, int c)\n{ a = c; b = new char; return a + *b; }" << (QList() << StateChange(Testbed::HeaderDoc, Range(1,8,1,11), "char", SHOULD_ASSIST)) << "class Foo {\nint bar(char a, char* b, int c = 10); \n};" << "int Foo::bar(char a, char* b, int c)\n{ a = c; b = new char; return a + *b; }"; QTest::newRow("prepend_arg_header") << "class Foo { void bar(int i); };" << "void Foo::bar(int i)\n{}" << (QList() << StateChange(Testbed::HeaderDoc, Range(0, 21, 0, 21), "char c, ", SHOULD_ASSIST)) << "class Foo { void bar(char c, int i); };" << "void Foo::bar(char c, int i)\n{}"; QTest::newRow("prepend_arg_cpp") << "class Foo { void bar(int i); };" << "void Foo::bar(int i)\n{}" << (QList() << StateChange(Testbed::CppDoc, Range(0, 14, 0, 14), "char c, ", SHOULD_ASSIST)) << "class Foo { void bar(char c, int i); };" << "void Foo::bar(char c, int i)\n{}"; QTest::newRow("change_default_parameter") << "class Foo {\nint bar(int a, char* b, int c = 10); \n};" << "int Foo::bar(int a, char* b, int c)\n{ a = c; b = new char; return a + *b; }" << (QList() << StateChange(Testbed::HeaderDoc, Range(1,29,1,34), "", NO_ASSIST)) << "class Foo {\nint bar(int a, char* b, int c); \n};" << "int Foo::bar(int a, char* b, int c)\n{ a = c; b = new char; return a + *b; }"; QTest::newRow("change_function_type") << "class Foo {\nint bar(int a, char* b, int c = 10); \n};" << "int Foo::bar(int a, char* b, int c)\n{ a = c; b = new char; return a + *b; }" << (QList() << StateChange(Testbed::CppDoc, Range(0,0,0,3), "char", SHOULD_ASSIST)) << "class Foo {\nchar bar(int a, char* b, int c = 10); \n};" << "char Foo::bar(int a, char* b, int c)\n{ a = c; b = new char; return a + *b; }"; QTest::newRow("swap_args_definition_side") << "class Foo {\nint bar(int a, char* b, int c = 10); \n};" << "int Foo::bar(int a, char* b, int c)\n{ a = c; b = new char; return a + *b; }" << (QList() << StateChange(Testbed::CppDoc, Range(0,13,0,28), "char* b, int a,", SHOULD_ASSIST)) << "class Foo {\nint bar(char* b, int a, int c = 10); \n};" << "int Foo::bar(char* b, int a, int c)\n{ a = c; b = new char; return a + *b; }"; // see https://bugs.kde.org/show_bug.cgi?id=299393 // actually related to the whitespaces in the header... QTest::newRow("change_function_constness") << "class Foo {\nvoid bar(const Foo&) const;\n};" << "void Foo::bar(const Foo&) const\n{}" << (QList() << StateChange(Testbed::CppDoc, Range(0,25,0,31), "", SHOULD_ASSIST)) << "class Foo {\nvoid bar(const Foo&);\n};" << "void Foo::bar(const Foo&)\n{}"; // see https://bugs.kde.org/show_bug.cgi?id=356179 QTest::newRow("keep_static_cpp") << "class Foo { static void bar(int i); };" << "void Foo::bar(int i)\n{}" << (QList() << StateChange(Testbed::CppDoc, Range(0, 19, 0, 19), ", char c", SHOULD_ASSIST)) << "class Foo { static void bar(int i, char c); };" << "void Foo::bar(int i, char c)\n{}"; QTest::newRow("keep_static_header") << "class Foo { static void bar(int i); };" << "void Foo::bar(int i)\n{}" << (QList() << StateChange(Testbed::HeaderDoc, Range(0, 33, 0, 33), ", char c", SHOULD_ASSIST)) << "class Foo { static void bar(int i, char c); };" << "void Foo::bar(int i, char c)\n{}"; // see https://bugs.kde.org/show_bug.cgi?id=356178 QTest::newRow("keep_default_args_cpp_before") << "class Foo { void bar(bool b, int i = 0); };" << "void Foo::bar(bool b, int i)\n{}" << (QList() << StateChange(Testbed::CppDoc, Range(0, 14, 0, 14), "char c, ", SHOULD_ASSIST)) << "class Foo { void bar(char c, bool b, int i = 0); };" << "void Foo::bar(char c, bool b, int i)\n{}"; QTest::newRow("keep_default_args_cpp_after") << "class Foo { void bar(bool b, int i = 0); };" << "void Foo::bar(bool b, int i)\n{}" << (QList() << StateChange(Testbed::CppDoc, Range(0, 27, 0, 27), ", char c", SHOULD_ASSIST)) << "class Foo { void bar(bool b, int i = 0, char c = {} /* TODO */); };" << "void Foo::bar(bool b, int i, char c)\n{}"; QTest::newRow("keep_default_args_header_before") << "class Foo { void bar(bool b, int i = 0); };" << "void Foo::bar(bool b, int i)\n{}" << (QList() << StateChange(Testbed::HeaderDoc, Range(0, 29, 0, 29), "char c = 'A', ", SHOULD_ASSIST)) << "class Foo { void bar(bool b, char c = 'A', int i = 0); };" << "void Foo::bar(bool b, char c, int i)\n{}"; QTest::newRow("keep_default_args_header_after") << "class Foo { void bar(bool b, int i = 0); };" << "void Foo::bar(bool b, int i)\n{}" << (QList() << StateChange(Testbed::HeaderDoc, Range(0, 38, 0, 38), ", char c = 'A'", SHOULD_ASSIST)) << "class Foo { void bar(bool b, int i = 0, char c = 'A'); };" << "void Foo::bar(bool b, int i, char c)\n{}"; // see https://bugs.kde.org/show_bug.cgi?id=355356 QTest::newRow("no_retval_on_ctor") << "class Foo { Foo(); };" << "Foo::Foo()\n{}" << (QList() << StateChange(Testbed::HeaderDoc, Range(0, 16, 0, 16), "char c", SHOULD_ASSIST)) << "class Foo { Foo(char c); };" << "Foo::Foo(char c)\n{}"; // see https://bugs.kde.org/show_bug.cgi?id=298511 QTest::newRow("change_return_type_header") << "struct Foo { int bar(); };" << "int Foo::bar()\n{}" << (QList() << StateChange(Testbed::HeaderDoc, Range(0, 13, 0, 16), "char", SHOULD_ASSIST)) << "struct Foo { char bar(); };" << "char Foo::bar()\n{}"; QTest::newRow("change_return_type_impl") << "struct Foo { int bar(); };" << "int Foo::bar()\n{}" << (QList() << StateChange(Testbed::CppDoc, Range(0, 0, 0, 3), "char", SHOULD_ASSIST)) << "struct Foo { char bar(); };" << "char Foo::bar()\n{}"; } void TestAssistants::testSignatureAssistant() { QFETCH(QString, headerContents); QFETCH(QString, cppContents); Testbed testbed(headerContents, cppContents); QExplicitlySharedDataPointer assistant; QFETCH(QList, stateChanges); foreach (StateChange stateChange, stateChanges) { testbed.changeDocument(stateChange.document, stateChange.range, stateChange.newText, true); const auto document = testbed.document(stateChange.document); QVERIFY(document); DUChainReadLocker lock; auto topCtx = DUChain::self()->chainForDocument(document->url()); QVERIFY(topCtx); const auto problem = findStaticAssistantProblem(DUChainUtils::allProblemsForContext(topCtx)); if (problem) { assistant = problem->solutionAssistant(); } if (stateChange.result == SHOULD_ASSIST) { #if CINDEX_VERSION_MINOR < 35 QEXPECT_FAIL("change_function_type", "Clang sees that return type of out-of-line definition differs from that in the declaration and won't parse the code...", Abort); QEXPECT_FAIL("change_return_type_impl", "Clang sees that return type of out-of-line definition differs from that in the declaration and won't include the function's AST and thus we never get updated about the new return type...", Abort); #endif QVERIFY(assistant && !assistant->actions().isEmpty()); } else { QVERIFY(!assistant || assistant->actions().isEmpty()); } } if (assistant && !assistant->actions().isEmpty()) assistant->actions().first()->execute(); QFETCH(QString, finalHeaderContents); QFETCH(QString, finalCppContents); QCOMPARE(testbed.documentText(Testbed::HeaderDoc), finalHeaderContents); QCOMPARE(testbed.documentText(Testbed::CppDoc), finalCppContents); } enum UnknownDeclarationAction { NoUnknownDeclarationAction = 0x0, ForwardDecls = 0x1, MissingInclude = 0x2 }; Q_DECLARE_FLAGS(UnknownDeclarationActions, UnknownDeclarationAction) Q_DECLARE_METATYPE(UnknownDeclarationActions) void TestAssistants::testUnknownDeclarationAssistant_data() { QTest::addColumn("headerContents"); QTest::addColumn("globalText"); QTest::addColumn("functionText"); QTest::addColumn("actions"); QTest::newRow("unincluded_struct") << "struct test{};" << "" << "test" << UnknownDeclarationActions(ForwardDecls | MissingInclude); QTest::newRow("forward_declared_struct") << "struct test{};" << "struct test;" << "test *f; f->" << UnknownDeclarationActions(MissingInclude); QTest::newRow("unknown_struct") << "" << "" << "test" << UnknownDeclarationActions(); QTest::newRow("not a class type") << "void test();" << "" << "test" << UnknownDeclarationActions(); } void TestAssistants::testUnknownDeclarationAssistant() { QFETCH(QString, headerContents); QFETCH(QString, globalText); QFETCH(QString, functionText); QFETCH(UnknownDeclarationActions, actions); static const auto cppContents = QStringLiteral("%1\nvoid f_u_n_c_t_i_o_n() {\n}"); Testbed testbed(headerContents, cppContents.arg(globalText), Testbed::NoAutoInclude); const auto document = testbed.document(Testbed::CppDoc); QVERIFY(document); const int line = document->lines() - 1; testbed.changeDocument(Testbed::CppDoc, Range(line, 0, line, 0), functionText, true); DUChainReadLocker lock; auto topCtx = DUChain::self()->chainForDocument(document->url()); QVERIFY(topCtx); const auto problems = topCtx->problems(); if (actions == NoUnknownDeclarationAction) { QVERIFY(!problems.isEmpty()); return; } auto firstProblem = problems.first(); auto assistant = firstProblem->solutionAssistant(); QVERIFY(assistant); const auto assistantActions = assistant->actions(); QStringList actionDescriptions; for (auto action: assistantActions) { actionDescriptions << action->description(); } { const bool hasForwardDecls = - actionDescriptions.contains(QObject::tr("Forward declare as 'struct'")) || - actionDescriptions.contains(QObject::tr("Forward declare as 'class'")); + actionDescriptions.contains(i18n("Forward declare as 'struct'")) || + actionDescriptions.contains(i18n("Forward declare as 'class'")); QCOMPARE(hasForwardDecls, static_cast(actions & ForwardDecls)); } { auto fileName = testbed.includeFileName(); fileName = fileName.mid(fileName.lastIndexOf('/') + 1); - const auto description = QObject::tr("Insert \'%1\'") - .arg(QStringLiteral("#include \"%1\"").arg(fileName)); + const auto directive = QStringLiteral("#include \"%1\"").arg(fileName); + const auto description = i18n("Insert \'%1\'", directive); const bool hasMissingInclude = actionDescriptions.contains(description); QCOMPARE(hasMissingInclude, static_cast(actions & MissingInclude)); } } void TestAssistants::testMoveIntoSource() { QFETCH(QString, origHeader); QFETCH(QString, origImpl); QFETCH(QString, newHeader); QFETCH(QString, newImpl); QFETCH(QualifiedIdentifier, id); TestFile header(origHeader, "h"); TestFile impl("#include \"" + header.url().byteArray() + "\"\n" + origImpl, "cpp", &header); { TopDUContext* headerCtx = nullptr; { DUChainReadLocker lock; headerCtx = DUChain::self()->chainForDocument(header.url()); } // Here is a problem: when launching tests one by one, we can reuse the same tmp file for headers. // But because of document chain for header wasn't unloaded properly in previous run we reuse it here // Therefore when using headerCtx->findDeclarations below we find declarations from the previous launch -> tests fail if (headerCtx) { // TODO: Investigate why this chain doesn't get updated when parsing source file DUChainWriteLocker lock; DUChain::self()->removeDocumentChain(headerCtx); } } impl.parse(KDevelop::TopDUContext::AllDeclarationsContextsAndUses); QVERIFY(impl.waitForParsed()); IndexedDeclaration declaration; { DUChainReadLocker lock; auto headerCtx = DUChain::self()->chainForDocument(header.url()); QVERIFY(headerCtx); auto decls = headerCtx->findDeclarations(id); Q_ASSERT(!decls.isEmpty()); declaration = IndexedDeclaration(decls.first()); QVERIFY(declaration.isValid()); } CodeRepresentation::setDiskChangesForbidden(false); ClangRefactoring refactoring; QCOMPARE(refactoring.moveIntoSource(declaration), QString()); CodeRepresentation::setDiskChangesForbidden(true); QCOMPARE(header.fileContents(), newHeader); QVERIFY(impl.fileContents().endsWith(newImpl)); } void TestAssistants::testMoveIntoSource_data() { QTest::addColumn("origHeader"); QTest::addColumn("origImpl"); QTest::addColumn("newHeader"); QTest::addColumn("newImpl"); QTest::addColumn("id"); const QualifiedIdentifier fooId("foo"); QTest::newRow("globalfunction") << QString("int foo()\n{\n int i = 0;\n return 0;\n}\n") << QString() << QString("int foo();\n") << QString("\nint foo()\n{\n int i = 0;\n return 0;\n}\n") << fooId; QTest::newRow("staticfunction") << QString("static int foo()\n{\n int i = 0;\n return 0;\n}\n") << QString() << QString("static int foo();\n") << QString("\nint foo()\n{\n int i = 0;\n return 0;\n}\n") << fooId; QTest::newRow("funcsameline") << QString("int foo() {\n int i = 0;\n return 0;\n}\n") << QString() << QString("int foo();\n") << QString("\nint foo() {\n int i = 0;\n return 0;\n}\n") << fooId; QTest::newRow("func-comment") << QString("int foo()\n/* foobar */ {\n int i = 0;\n return 0;\n}\n") << QString() << QString("int foo()\n/* foobar */;\n") << QString("\nint foo() {\n int i = 0;\n return 0;\n}\n") << fooId; QTest::newRow("func-comment2") << QString("int foo()\n/*asdf*/\n{\n int i = 0;\n return 0;\n}\n") << QString() << QString("int foo()\n/*asdf*/;\n") << QString("\nint foo()\n{\n int i = 0;\n return 0;\n}\n") << fooId; const QualifiedIdentifier aFooId("a::foo"); QTest::newRow("class-method") << QString("class a {\n int foo(){\n return 0;\n }\n};\n") << QString() << QString("class a {\n int foo();\n};\n") << QString("\nint a::foo() {\n return 0;\n }\n") << aFooId; QTest::newRow("class-method-const") << QString("class a {\n int foo() const\n {\n return 0;\n }\n};\n") << QString() << QString("class a {\n int foo() const;\n};\n") << QString("\nint a::foo() const\n {\n return 0;\n }\n") << aFooId; QTest::newRow("class-method-const-sameline") << QString("class a {\n int foo() const{\n return 0;\n }\n};\n") << QString() << QString("class a {\n int foo() const;\n};\n") << QString("\nint a::foo() const {\n return 0;\n }\n") << aFooId; QTest::newRow("elaborated-type") << QString("namespace NS{class C{};} class a {\nint foo(const NS::C c) const{\nreturn 0;\n}\n};\n") << QString() << QString("namespace NS{class C{};} class a {\nint foo(const NS::C c) const;\n};\n") << QString("\nint a::foo(const NS::C c) const {\nreturn 0;\n}\n") << aFooId; QTest::newRow("add-into-namespace") << QString("namespace NS{class a {\nint foo() const {\nreturn 0;\n}\n};\n}") << QString("namespace NS{\n}") << QString("namespace NS{class a {\nint foo() const;\n};\n}") << QString("namespace NS{\n\nint a::foo() const {\nreturn 0;\n}\n}") << QualifiedIdentifier("NS::a::foo"); QTest::newRow("class-template-parameter") << QString(R"( namespace first { template class Test{}; namespace second { template class List; } class MoveIntoSource { public: void f(const second::List>*>& param){} };} )") << QString("") << QString(R"( namespace first { template class Test{}; namespace second { template class List; } class MoveIntoSource { public: void f(const second::List>*>& param); };} )") << QString("namespace first {\nvoid MoveIntoSource::f(const first::second::List< const volatile first::Test< first::second::List< int* > >* >& param) {}}\n\n") << QualifiedIdentifier("first::MoveIntoSource::f"); QTest::newRow("move-unexposed-type") << QString("namespace std { template class basic_string; \ntypedef basic_string string;}\n void move(std::string i){}") << QString("") << QString("namespace std { template class basic_string; \ntypedef basic_string string;}\n void move(std::string i);") << QString("void move(std::string i) {}\n") << QualifiedIdentifier("move"); QTest::newRow("move-constructor") << QString("class Class{Class(){}\n};") << QString("") << QString("class Class{Class();\n};") << QString("Class::Class() {}\n") << QualifiedIdentifier("Class::Class"); } diff --git a/languages/clang/tests/test_files.cpp b/languages/clang/tests/test_files.cpp index 8560cf7e48..219f230937 100644 --- a/languages/clang/tests/test_files.cpp +++ b/languages/clang/tests/test_files.cpp @@ -1,95 +1,101 @@ /************************************************************************************* * Copyright (C) 2013 by Milian Wolff * * Copyright (C) 2013 Olivier de Gaalon * * * * 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 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_files.h" #include #include #include #include #include #include #include #include "testfilepaths.h" //Include all used json tests, otherwise "Test not found" #include #include #include #include #include #include using namespace KDevelop; QTEST_MAIN(TestFiles) void TestFiles::initTestCase() { qputenv("KDEV_CLANG_JSON_TEST_RUN", "1"); qputenv("KDEV_CLANG_EXTRA_ARGUMENTS", "-Wno-unused-variable -Wno-unused-parameter -Wno-unused-comparison -Wno-unused-value -Wno-unused-private-field -Wno-ignored-attributes"); QLoggingCategory::setFilterRules(QStringLiteral("*.debug=false\ndefault.debug=true\nkdevelop.plugins.clang.debug=true\n")); QVERIFY(qputenv("KDEV_DISABLE_PLUGINS", "kdevcppsupport")); QVERIFY(qputenv("KDEV_CLANG_DISPLAY_DIAGS", "1")); AutoTestShell::init({"kdevclangsupport"}); TestCore::initialize(Core::NoUi); DUChain::self()->disablePersistentStorage(); Core::self()->languageController()->backgroundParser()->setDelay(0); CodeRepresentation::setDiskChangesForbidden(true); } void TestFiles::cleanupTestCase() { TestCore::shutdown(); } void TestFiles::testFiles_data() { QTest::addColumn("fileName"); const QString testDirPath = TEST_FILES_DIR; const QStringList files = QDir(testDirPath).entryList({"*.h", "*.cpp", "*.c", "*.cl", "*.cu"}, QDir::Files); foreach (const QString& file, files) { QTest::newRow(file.toUtf8().constData()) << QString(testDirPath + '/' + file); } } void TestFiles::testFiles() { QFETCH(QString, fileName); const IndexedString indexedFileName(fileName); ReferencedTopDUContext top = DUChain::self()->waitForUpdate(indexedFileName, TopDUContext::AllDeclarationsContextsAndUses); + if (strcmp("test.cl", QTest::currentDataTag()) == 0) { + if (!top) { + QSKIP("Likely outdated shared-mime-info around, which doesn't know about the text/x-opencl-src mime type"); + } + } + QVERIFY(top); DUChainReadLocker lock; DeclarationValidator validator; top->visit(validator); QVERIFY(validator.testsPassed()); foreach(auto problem, top->problems()) { qDebug() << problem; } if (!QTest::currentDataTag() || strcmp("invalid.cpp", QTest::currentDataTag()) != 0) { QVERIFY(top->problems().isEmpty()); } } diff --git a/languages/plugins/custom-definesandincludes/kcm_widget/projectpathswidget.cpp b/languages/plugins/custom-definesandincludes/kcm_widget/projectpathswidget.cpp index 95be4a0319..ad54f58633 100644 --- a/languages/plugins/custom-definesandincludes/kcm_widget/projectpathswidget.cpp +++ b/languages/plugins/custom-definesandincludes/kcm_widget/projectpathswidget.cpp @@ -1,306 +1,306 @@ /************************************************************************ * * * Copyright 2010 Andreas Pakulat * * Copyright 2014 Sergey Kalinichev * * * * 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 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, see . * ************************************************************************/ #include "projectpathswidget.h" #include #include #include #include #include #include #include #include "../compilerprovider/compilerprovider.h" #include "../compilerprovider/settingsmanager.h" #include "ui_projectpathswidget.h" #include "ui_batchedit.h" #include "projectpathsmodel.h" #include "debugarea.h" using namespace KDevelop; namespace { enum PageType { IncludesPage, DefinesPage, ParserArgumentsPage }; } ProjectPathsWidget::ProjectPathsWidget( QWidget* parent ) : QWidget(parent), ui(new Ui::ProjectPathsWidget), pathsModel(new ProjectPathsModel(this)) { ui->setupUi( this ); ui->addPath->setIcon(QIcon::fromTheme("list-add")); ui->removePath->setIcon(QIcon::fromTheme("list-remove")); // hack taken from kurlrequester, make the buttons a bit less in height so they better match the url-requester ui->addPath->setFixedHeight( ui->projectPaths->sizeHint().height() ); ui->removePath->setFixedHeight( ui->projectPaths->sizeHint().height() ); connect( ui->addPath, &QPushButton::clicked, this, &ProjectPathsWidget::addProjectPath ); connect( ui->removePath, &QPushButton::clicked, this, &ProjectPathsWidget::deleteProjectPath ); connect( ui->batchEdit, &QPushButton::clicked, this, &ProjectPathsWidget::batchEdit ); ui->projectPaths->setModel( pathsModel ); connect( ui->projectPaths, static_cast(&KComboBox::currentIndexChanged), this, &ProjectPathsWidget::projectPathSelected ); connect( pathsModel, &ProjectPathsModel::dataChanged, this, &ProjectPathsWidget::changed ); connect( pathsModel, &ProjectPathsModel::rowsInserted, this, &ProjectPathsWidget::changed ); connect( pathsModel, &ProjectPathsModel::rowsRemoved, this, &ProjectPathsWidget::changed ); connect( ui->compiler, static_cast(&QComboBox::activated), this, &ProjectPathsWidget::changed ); connect( ui->compiler, static_cast(&QComboBox::activated), this, &ProjectPathsWidget::changeCompilerForPath ); connect( ui->includesWidget, static_cast(&IncludesWidget::includesChanged), this, &ProjectPathsWidget::includesChanged ); connect( ui->definesWidget, static_cast(&DefinesWidget::definesChanged), this, &ProjectPathsWidget::definesChanged ); connect(ui->languageParameters, &QTabWidget::currentChanged, this, &ProjectPathsWidget::tabChanged); connect(ui->parserWidget, &ParserWidget::changed, this, &ProjectPathsWidget::parserArgumentsChanged); tabChanged(IncludesPage); } QVector ProjectPathsWidget::paths() const { return pathsModel->paths(); } void ProjectPathsWidget::setPaths( const QVector& paths ) { bool b = blockSignals( true ); clear(); pathsModel->setPaths( paths ); blockSignals( b ); ui->projectPaths->setCurrentIndex(0); // at least a project root item is present ui->languageParameters->setCurrentIndex(0); // Set compilers ui->compiler->clear(); auto settings = SettingsManager::globalInstance(); auto compilers = settings->provider()->compilers(); for (int i = 0 ; i < compilers.count(); ++i) { Q_ASSERT(compilers[i]); if (!compilers[i]) { continue; } ui->compiler->addItem(compilers[i]->name()); QVariant val; val.setValue(compilers[i]); ui->compiler->setItemData(i, val); } projectPathSelected(0); updateEnablements(); } void ProjectPathsWidget::definesChanged( const Defines& defines ) { definesAndIncludesDebug() << "defines changed"; updatePathsModel( QVariant::fromValue(defines), ProjectPathsModel::DefinesDataRole ); } void ProjectPathsWidget::includesChanged( const QStringList& includes ) { definesAndIncludesDebug() << "includes changed"; updatePathsModel( includes, ProjectPathsModel::IncludesDataRole ); } void ProjectPathsWidget::parserArgumentsChanged() { updatePathsModel(QVariant::fromValue(ui->parserWidget->parserArguments()), ProjectPathsModel::ParserArgumentsRole); } void ProjectPathsWidget::updatePathsModel(const QVariant& newData, int role) { QModelIndex idx = pathsModel->index( ui->projectPaths->currentIndex(), 0, QModelIndex() ); if( idx.isValid() ) { bool b = pathsModel->setData( idx, newData, role ); if( b ) { emit changed(); } } } void ProjectPathsWidget::projectPathSelected( int index ) { if( index < 0 && pathsModel->rowCount() > 0 ) { index = 0; } Q_ASSERT(index >= 0); const QModelIndex midx = pathsModel->index( index, 0 ); ui->includesWidget->setIncludes( pathsModel->data( midx, ProjectPathsModel::IncludesDataRole ).toStringList() ); ui->definesWidget->setDefines( pathsModel->data( midx, ProjectPathsModel::DefinesDataRole ).value() ); Q_ASSERT(pathsModel->data(midx, ProjectPathsModel::CompilerDataRole).value()); ui->compiler->setCurrentText(pathsModel->data(midx, ProjectPathsModel::CompilerDataRole).value()->name()); ui->parserWidget->setParserArguments(pathsModel->data(midx, ProjectPathsModel::ParserArgumentsRole).value()); updateEnablements(); } void ProjectPathsWidget::clear() { bool sigDisabled = ui->projectPaths->blockSignals( true ); pathsModel->setPaths({}); ui->includesWidget->clear(); ui->definesWidget->clear(); updateEnablements(); ui->projectPaths->blockSignals( sigDisabled ); } void ProjectPathsWidget::addProjectPath() { const QUrl directory = pathsModel->data(pathsModel->index(0, 0), ProjectPathsModel::FullUrlDataRole).value(); - QFileDialog dlg(this, tr("Select Project Path"), directory.toLocalFile()); + QFileDialog dlg(this, i18n("Select Project Path"), directory.toLocalFile()); dlg.setFileMode(QFileDialog::Directory); dlg.setOption(QFileDialog::ShowDirsOnly); dlg.exec(); pathsModel->addPath(dlg.selectedUrls().value(0)); ui->projectPaths->setCurrentIndex(pathsModel->rowCount() - 1); updateEnablements(); } void ProjectPathsWidget::deleteProjectPath() { const QModelIndex idx = pathsModel->index( ui->projectPaths->currentIndex(), 0 ); if( KMessageBox::questionYesNo( this, i18n("Are you sure you want to remove the configuration for the path '%1'?", pathsModel->data( idx, Qt::DisplayRole ).toString() ), "Remove Path Configuration" ) == KMessageBox::Yes ) { pathsModel->removeRows( ui->projectPaths->currentIndex(), 1 ); } updateEnablements(); } void ProjectPathsWidget::setProject(KDevelop::IProject* w_project) { pathsModel->setProject( w_project ); ui->includesWidget->setProject( w_project ); } void ProjectPathsWidget::updateEnablements() { // Disable removal of the project root entry which is always first in the list ui->removePath->setEnabled( ui->projectPaths->currentIndex() > 0 ); } void ProjectPathsWidget::batchEdit() { Ui::BatchEdit be; QDialog dialog(this); be.setupUi(&dialog); const int index = qMax(ui->projectPaths->currentIndex(), 0); const QModelIndex midx = pathsModel->index(index, 0); if (!midx.isValid()) { return; } bool includesTab = ui->languageParameters->currentIndex() == 0; if (includesTab) { auto includes = pathsModel->data(midx, ProjectPathsModel::IncludesDataRole).toStringList(); be.textEdit->setPlainText(includes.join("\n")); dialog.setWindowTitle(i18n("Edit include directories/files")); } else { auto defines = pathsModel->data(midx, ProjectPathsModel::DefinesDataRole).value(); for (auto it = defines.constBegin(); it != defines.constEnd(); it++) { be.textEdit->append(it.key() + "=" + it.value()); } dialog.setWindowTitle(i18n("Edit defined macros")); } if (dialog.exec() != QDialog::Accepted) { return; } if (includesTab) { auto includes = be.textEdit->toPlainText().split('\n', QString::SkipEmptyParts); for (auto& s : includes) { s = s.trimmed(); } pathsModel->setData(midx, includes, ProjectPathsModel::IncludesDataRole); } else { auto list = be.textEdit->toPlainText().split('\n', QString::SkipEmptyParts); Defines defines; for (auto& d : list) { //This matches: a=b, a=, a QRegExp r("^([^=]+)(=(.*))?$"); if (!r.exactMatch(d)) { continue; } defines[r.cap(1).trimmed()] = r.cap(3).trimmed(); } pathsModel->setData(midx, QVariant::fromValue(defines), ProjectPathsModel::DefinesDataRole); } projectPathSelected(index); } void ProjectPathsWidget::setCurrentCompiler(const QString& name) { for (int i = 0 ; i < ui->compiler->count(); ++i) { if(ui->compiler->itemText(i) == name) { ui->compiler->setCurrentIndex(i); } } } CompilerPointer ProjectPathsWidget::currentCompiler() const { return ui->compiler->itemData(ui->compiler->currentIndex()).value(); } void ProjectPathsWidget::tabChanged(int idx) { if (idx == ParserArgumentsPage) { ui->batchEdit->setVisible(false); ui->compilerBox->setVisible(true); ui->configureLabel->setText(i18n("Configure C/C++ parser")); } else { ui->batchEdit->setVisible(true); ui->compilerBox->setVisible(false); ui->configureLabel->setText(i18n("Configure which macros and include directories/files will be added to the parser during project parsing:")); } } void ProjectPathsWidget::changeCompilerForPath() { for (int idx = 0; idx < pathsModel->rowCount(); idx++) { const QModelIndex midx = pathsModel->index(idx, 0); if (pathsModel->data(midx, Qt::DisplayRole) == ui->projectPaths->currentText()) { pathsModel->setData(midx, QVariant::fromValue(currentCompiler()), ProjectPathsModel::CompilerDataRole); break; } } } diff --git a/languages/plugins/custom-definesandincludes/noprojectincludesanddefines/noprojectcustomincludepaths.cpp b/languages/plugins/custom-definesandincludes/noprojectincludesanddefines/noprojectcustomincludepaths.cpp index 09c0aa5a73..1f7c133f94 100644 --- a/languages/plugins/custom-definesandincludes/noprojectincludesanddefines/noprojectcustomincludepaths.cpp +++ b/languages/plugins/custom-definesandincludes/noprojectincludesanddefines/noprojectcustomincludepaths.cpp @@ -1,77 +1,77 @@ /* * Copyright 2014 Kevin Funk * * 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 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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 . * */ #include "noprojectcustomincludepaths.h" #include "ui_noprojectcustomincludepaths.h" #include #include #include NoProjectCustomIncludePaths::NoProjectCustomIncludePaths(QWidget* parent) : QDialog(parent), m_ui(new Ui::CustomIncludePaths) { m_ui->setupUi(this); m_ui->storageDirectory->setMode(KFile::Directory); setWindowTitle(i18n("Setup Custom Include Paths")); connect(m_ui->directorySelector, &QPushButton::clicked, this, &NoProjectCustomIncludePaths::openAddIncludeDirectoryDialog); } void NoProjectCustomIncludePaths::setStorageDirectory(const QString& path) { m_ui->storageDirectory->setUrl(QUrl::fromLocalFile(path)); } QString NoProjectCustomIncludePaths::storageDirectory() const { return m_ui->storageDirectory->url().toLocalFile(); } void NoProjectCustomIncludePaths::appendCustomIncludePath(const QString& path) { m_ui->customIncludePaths->appendPlainText(path); } QStringList NoProjectCustomIncludePaths::customIncludePaths() const { const QString pathsText = m_ui->customIncludePaths->document()->toPlainText(); const QStringList paths = pathsText.split('\n', QString::SkipEmptyParts); return paths; } void NoProjectCustomIncludePaths::setCustomIncludePaths(const QStringList& paths) { m_ui->customIncludePaths->setPlainText(paths.join("\n")); } void NoProjectCustomIncludePaths::openAddIncludeDirectoryDialog() { - const QString dirName = QFileDialog::getExistingDirectory(this, tr("Select directory to include")); + const QString dirName = QFileDialog::getExistingDirectory(this, i18n("Select directory to include")); if (dirName.isEmpty()) return; appendCustomIncludePath(dirName); } diff --git a/projectbuilders/ninjabuilder/ninjajob.cpp b/projectbuilders/ninjabuilder/ninjajob.cpp index a5860c2c4a..c9e8314079 100644 --- a/projectbuilders/ninjabuilder/ninjajob.cpp +++ b/projectbuilders/ninjabuilder/ninjajob.cpp @@ -1,234 +1,241 @@ /* This file is part of KDevelop Copyright 2012 Aleix Pol Gonzalez Copyright 2017 Kevin Funk This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. 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 "ninjajob.h" #include "ninjabuilder.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; class NinjaJobCompilerFilterStrategy : public CompilerFilterStrategy { public: using CompilerFilterStrategy::CompilerFilterStrategy; IFilterStrategy::Progress progressInLine(const QString& line) override; }; IFilterStrategy::Progress NinjaJobCompilerFilterStrategy::progressInLine(const QString& line) { // example string: [87/88] Building CXX object projectbuilders/ninjabuilder/CMakeFiles/kdevninja.dir/ninjajob.cpp.o static const QRegularExpression re("^\\[([0-9]+)\\/([0-9]+)\\] (.*)"); QRegularExpressionMatch match = re.match(line); if (match.hasMatch()) { const int current = match.capturedRef(1).toInt(); const int total = match.capturedRef(2).toInt(); if (current && total) { // this is output from ninja const QString action = match.captured(3); const int percent = qRound(( float )current / total * 100); return { action, percent }; } } return {}; } NinjaJob::NinjaJob(KDevelop::ProjectBaseItem* item, CommandType commandType, const QStringList& arguments, const QByteArray& signal, NinjaBuilder* parent) : OutputExecuteJob(parent) , m_isInstalling(false) , m_idx(item->index()) , m_commandType(commandType) , m_signal(signal) , m_plugin(parent) { auto bsm = item->project()->buildSystemManager(); auto buildDir = bsm->buildDirectory(item); setToolTitle(i18n("Ninja")); setCapabilities(Killable); setStandardToolView(KDevelop::IOutputView::BuildView); setBehaviours(KDevelop::IOutputView::AllowUserClose | KDevelop::IOutputView::AutoScroll); setFilteringStrategy(new NinjaJobCompilerFilterStrategy(buildDir.toUrl())); setProperties(NeedWorkingDirectory | PortableMessages | DisplayStderr | IsBuilderHint | PostProcessOutput); // hardcode the ninja output format so we can parse it reliably addEnvironmentOverride(QStringLiteral("NINJA_STATUS"), QStringLiteral("[%s/%t] ")); *this << ninjaExecutable(); *this << arguments; QStringList targets; foreach (const QString& arg, arguments) { if (!arg.startsWith('-')) { targets << arg; } } QString title; if (!targets.isEmpty()) { title = i18n("Ninja (%1): %2", item->text(), targets.join(" ")); } else { title = i18n("Ninja (%1)", item->text()); } setJobName(title); connect(this, &NinjaJob::finished, this, &NinjaJob::emitProjectBuilderSignal); } +NinjaJob::~NinjaJob() +{ + // prevent crash when emitting KJob::finished from ~KJob + // (=> at this point NinjaJob is already destructed...) + disconnect(this, &NinjaJob::finished, this, &NinjaJob::emitProjectBuilderSignal); +} + void NinjaJob::setIsInstalling(bool isInstalling) { m_isInstalling = isInstalling; } QString NinjaJob::ninjaExecutable() { QString path = QStandardPaths::findExecutable("ninja-build"); if (path.isEmpty()) { path = QStandardPaths::findExecutable("ninja"); } return path; } QUrl NinjaJob::workingDirectory() const { KDevelop::ProjectBaseItem* it = item(); if (!it) { return QUrl(); } KDevelop::IBuildSystemManager* bsm = it->project()->buildSystemManager(); KDevelop::Path workingDir = bsm->buildDirectory(it); while (!QFile::exists(workingDir.toLocalFile() + "build.ninja")) { KDevelop::Path upWorkingDir = workingDir.parent(); if (!upWorkingDir.isValid() || upWorkingDir == workingDir) { return bsm->buildDirectory(it->project()->projectItem()).toUrl(); } workingDir = upWorkingDir; } return workingDir.toUrl(); } QStringList NinjaJob::privilegedExecutionCommand() const { KDevelop::ProjectBaseItem* it = item(); if (!it) { return QStringList(); } KSharedConfigPtr configPtr = it->project()->projectConfiguration(); KConfigGroup builderGroup(configPtr, "NinjaBuilder"); bool runAsRoot = builderGroup.readEntry("Install As Root", false); if (runAsRoot && m_isInstalling) { int suCommand = builderGroup.readEntry("Su Command", 0); QStringList arguments; QString suCommandName; switch (suCommand) { case 1: return QStringList() << "kdesudo" << "-t"; case 2: return QStringList() << "sudo"; default: return QStringList() << "kdesu" << "-t"; } } return QStringList(); } void NinjaJob::emitProjectBuilderSignal(KJob* job) { if (!m_plugin) { return; } KDevelop::ProjectBaseItem* it = item(); if (!it) { return; } if (job->error() == 0) { Q_ASSERT(!m_signal.isEmpty()); QMetaObject::invokeMethod(m_plugin, m_signal, Q_ARG(KDevelop::ProjectBaseItem*, it)); } else { QMetaObject::invokeMethod(m_plugin, "failed", Q_ARG(KDevelop::ProjectBaseItem*, it)); } } void NinjaJob::postProcessStderr(const QStringList& lines) { appendLines(lines); } void NinjaJob::postProcessStdout(const QStringList& lines) { appendLines(lines); } void NinjaJob::appendLines(const QStringList& lines) { if (lines.isEmpty()) { return; } QStringList ret(lines); bool prev = false; for (QStringList::iterator it = ret.end(); it != ret.begin(); ) { --it; bool curr = it->startsWith('['); if ((prev && curr) || it->endsWith("] ")) { it = ret.erase(it); } prev = curr; } model()->appendLines(ret); } KDevelop::ProjectBaseItem* NinjaJob::item() const { return KDevelop::ICore::self()->projectController()->projectModel()->itemFromIndex(m_idx); } NinjaJob::CommandType NinjaJob::commandType() const { return m_commandType; } diff --git a/projectbuilders/ninjabuilder/ninjajob.h b/projectbuilders/ninjabuilder/ninjajob.h index bcc84fd750..53c7ec16fa 100644 --- a/projectbuilders/ninjabuilder/ninjajob.h +++ b/projectbuilders/ninjabuilder/ninjajob.h @@ -1,83 +1,84 @@ /* This file is part of KDevelop Copyright 2012 Aleix Pol Gonzalez Copyright 2017 Kevin Funk This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. 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 NINJAJOB_H #define NINJAJOB_H #include #include namespace KDevelop { class OutputModel; class ProjectBaseItem; } class NinjaBuilder; class QUrl; class NinjaJob : public KDevelop::OutputExecuteJob { Q_OBJECT public: enum CommandType { BuildCommand, CleanCommand, CustomTargetCommand, InstallCommand }; enum ErrorTypes { Correct = 0, Failed }; public: NinjaJob(KDevelop::ProjectBaseItem* item, CommandType commandType, const QStringList& arguments, const QByteArray& signal, NinjaBuilder* parent); + ~NinjaJob() override; void setIsInstalling(bool isInstalling); static QString ninjaExecutable(); KDevelop::ProjectBaseItem* item() const; CommandType commandType() const; QUrl workingDirectory() const override; QStringList privilegedExecutionCommand() const override; protected slots: void postProcessStdout(const QStringList& lines) override; void postProcessStderr(const QStringList& lines) override; private slots: void emitProjectBuilderSignal(KJob* job); private: bool m_isInstalling; QPersistentModelIndex m_idx; CommandType m_commandType; QByteArray m_signal; QPointer m_plugin; void appendLines(const QStringList& lines); }; #endif // NINJAJOB_H diff --git a/release-scripts/REPOSITORIES.inc b/release-scripts/REPOSITORIES.inc new file mode 100644 index 0000000000..21df10be2c --- /dev/null +++ b/release-scripts/REPOSITORIES.inc @@ -0,0 +1 @@ +kdevplatform kdevelop kdev-python kdev-php diff --git a/release-scripts/VERSIONS.ini b/release-scripts/VERSIONS.ini new file mode 100644 index 0000000000..509ab402cb --- /dev/null +++ b/release-scripts/VERSIONS.ini @@ -0,0 +1,8 @@ +[default] +MAJOR_VERSION=5 +MINOR_VERSION=1 +PATCH_VERSION=0 +BRANCH=5.1 + +OLD_SHA1=5.0 +NEW_SHA1=v5.1.0 diff --git a/release-scripts/create_log.py b/release-scripts/create_log.py new file mode 100755 index 0000000000..2baaf5e159 --- /dev/null +++ b/release-scripts/create_log.py @@ -0,0 +1,137 @@ +#!/usr/bin/env python3 + +# stolen from releaseme/plasma, which was stolen from release-tools Applications/15.04 branch +# ported to Python 3 and fixed the worst issues + removed Plasma-related bits --Kevin + +from __future__ import print_function + +import configparser +import os +import subprocess +import sys +import cgi + +THIS_DIR = os.path.dirname(os.path.realpath(__file__)) + +f = open(os.path.join(THIS_DIR, 'REPOSITORIES.inc')) +srcdir = os.getcwd() +repos = f.read().rstrip().split(" ") + +for repo in repos: + config = configparser.ConfigParser() + config.read(os.path.join(THIS_DIR, "VERSIONS.ini")) + fromVersion = config['default']['OLD_SHA1'] + toVersion = config['default']['NEW_SHA1'] + + os.chdir(os.path.join(srcdir, repo)) + + p = subprocess.Popen('git fetch', shell=True, + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + if p.wait() != 0: + raise NameError('git fetch failed') + + p = subprocess.Popen('git rev-parse ' + fromVersion + ' ' + toVersion, shell=True, + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + if p.wait() != 0: + raise NameError("git rev-parse failed -- correct to/from version?") + + p = subprocess.Popen('git log ' + fromVersion + '...' + toVersion, shell=True, + stdout=subprocess.PIPE, universal_newlines=True) + commit = [] + commits = [] + for line in p.stdout: + if line.startswith("commit"): + if len(commit) > 1 and not ignoreCommit: + commits.append(commit) + commitHash = line[7:].strip() + ignoreCommit = False + commit = [commitHash] + elif line.startswith("Author"): + pass + elif line.startswith("Date"): + pass + elif line.startswith("Merge"): + pass + else: + line = line.strip() + if line.startswith("Merge remote-tracking branch"): + ignoreCommit = True + elif line.startswith("SVN_SILENT"): + ignoreCommit = True + elif line.startswith("GIT_SILENT"): + ignoreCommit = True + elif line.startswith("Merge branch"): + ignoreCommit = True + elif line.startswith("Update version number for"): + ignoreCommit = True + elif line: + commit.append(line) + # Add the last commit + if len(commit) > 1 and not ignoreCommit: + commits.append(commit) + + if len(commits): + print("

" + repo + "

") + print("
    ") + for commit in commits: + extra = "" + changelog = commit[1] + + for line in commit: + line = cgi.escape(line) + if line.startswith("BUGS:"): + bugNumbers = line[line.find(":") + 1:].strip() + for bugNumber in bugNumbers.split(","): + if bugNumber.isdigit(): + if extra: + extra += ". " + extra += "Fixes bug #" + bugNumber + "" + elif line.startswith("BUG:"): + bugNumber = line[line.find(":") + 1:].strip() + if bugNumber.isdigit(): + if extra: + extra += ". " + extra += "Fixes bug #" + bugNumber + "" + elif line.startswith("REVIEW:"): + if extra: + extra += ". " + reviewNumber = line[line.find(":") + 1:].strip() + extra += "Code review #" + reviewNumber + "" + # jr addition 2017-02 phab link + elif line.startswith("Differential Revision:"): + if extra: + extra += ". " + reviewNumber = line[line.find("org/") + 4:].strip() + extra += "Phabricator Code review " + reviewNumber + "" + elif line.startswith("CCBUG:"): + if extra: + extra += ". " + bugNumber = line[line.find(":") + 1:].strip() + extra += "See bug #" + bugNumber + "" + elif line.startswith("FEATURE:"): + feature = line[line.find(":") + 1:].strip() + if feature.isdigit(): + if extra: + extra += ". " + extra += "Implements feature #" + feature + "" + else: + if feature: + changelog = feature + + elif line.startswith("CHANGELOG:"): + changelog = line[11:] # remove word "CHANGELOG: " + elif line.startswith("Merge Plasma"): + pass + + commitHash = commit[0] + if not changelog.endswith("."): + changelog = changelog + "." + + # NOTE: Only showing interesting changes + if extra: + capitalizedChangelog = changelog[0].capitalize() + changelog[1:] + print("
  • " + capitalizedChangelog + " Commit. " + extra + "
  • ") + print("
\n\n") + + if p.wait() != 0: + raise NameError('git log failed', repo, fromVersion, toVersion) diff --git a/release-scripts/update-versions.sh b/release-scripts/update-versions.sh index 9ae9f260da..261e3853a3 100755 --- a/release-scripts/update-versions.sh +++ b/release-scripts/update-versions.sh @@ -1,27 +1,25 @@ #!/bin/bash set -x -MAJOR_VERSION=5 -MINOR_VERSION=1 -PATCH_VERSION=40 -BRANCH=master +# load ini file +source <(grep = VERSIONS.ini) do_replace() { local project=$1 echo $MINOR_VERSION git checkout $BRANCH sed -i -e "s/set(.*${project}_VERSION_MAJOR .*)/set(${project}_VERSION_MAJOR $MAJOR_VERSION)/g" CMakeLists.txt sed -i -e "s/set(.*${project}_VERSION_MINOR .*)/set(${project}_VERSION_MINOR $MINOR_VERSION)/g" CMakeLists.txt sed -i -e "s/set(.*${project}_VERSION_PATCH .*)/set(${project}_VERSION_PATCH $PATCH_VERSION)/g" CMakeLists.txt git diff CMakeLists.txt git commit CMakeLists.txt -m "Update version number to $MAJOR_VERSION.$MINOR_VERSION.$PATCH_VERSION" git push origin $BRANCH } (cd kdevplatform; do_replace KDEVPLATFORM) (cd kdevelop; do_replace KDEVELOP) (cd kdev-python; do_replace KDEVPYTHON) (cd kdev-php; do_replace KDEVPHP)