diff --git a/appimage/Dockerfile b/appimage/Dockerfile index cb204c6f9f..bf61055942 100644 --- a/appimage/Dockerfile +++ b/appimage/Dockerfile @@ -1,39 +1,39 @@ FROM centos:6.10 RUN yum -y update && yum -y install wget && yum -y install centos-release-scl epel-release && yum -y update && yum -y install devtoolset-6-gcc devtoolset-6-gcc-c++ devtoolset-6-binutils wget tar bzip2 git libtool which fuse fuse-devel libpng-devel automake cppunit-devel cmake glibc-headers libstdc++-devel gcc-c++ freetype-devel fontconfig-devel libxml2-devel libstdc++-devel libXrender-devel patch xcb-util-keysyms-devel libXi-devel libudev-devel.x86_64 openssl-devel sqlite-devel.x86_64 gperftools.x86_64 gperf.x86_64 libicu-devel.x86_64 boost-devel.x86_64 libxslt-devel.x86_64 docbook-style-xsl.noarch python27.x86_64 cmake3.x86_64 ruby bison flex bison-devel ruby-devel flex-devel xz xz-devel pcre-devel pcre2-devel pcre pcre2 mesa-libEGL-devel mesa-libGL-devel glib-devel gettext perl-URI.noarch bzip2-devel.x86_64 subversion-devel.x86_64 subversion.x86_64 sqlite2-devel.x86_64 hunspell-devel aspell-devel hspell-devel vim sudo unzip xkeyboard-config RUN cd /tmp && wget http://opensource.wandisco.com/rhel/6/svn-1.9/RPMS/x86_64/subversion-1.9.4-3.x86_64.rpm http://opensource.wandisco.com/rhel/6/svn-1.9/RPMS/x86_64/subversion-devel-1.9.4-3.x86_64.rpm http://opensource.wandisco.com/rhel/6/svn-1.9/RPMS/x86_64/serf-1.3.7-1.x86_64.rpm && yum -y install subversion* serf*; rm subversion* serf* RUN echo ". /opt/rh/devtoolset-6/enable && chmod +x /opt/rh/python27/enable && . /opt/rh/python27/enable" >> /root/.bashrc -ENV LC_ALL=en_US.UTF-8 LANG=en_us.UTF-8 PYTHON_VERSION=3.6.7 QTVERSION=5.9.7 QTVERSION_SHORT=5.9 LLVM_VERSION=6.0.1 LLVM_ROOT=/opt/llvm/ LD_LIBRARY_PATH=$QTDIR/lib/ +ENV LANG=en_US.UTF-8 PYTHON_VERSION=3.6.7 QTVERSION=5.9.8 QTVERSION_SHORT=5.9 LLVM_VERSION=8.0.0 LLVM_ROOT=/opt/llvm/ LD_LIBRARY_PATH=$QTDIR/lib/ ENV QTDIR=/opt/qt5 RUN bash -c "ln -sf /opt/rh/devtoolset-6/root/usr/bin/g++ /usr/bin/g++ && ln -sf /opt/rh/devtoolset-6/root/usr/bin/c++ /usr/bin/c++" # Build Qt5 -RUN bash -c "mkdir -p /qt && cd /qt && wget http://download.qt.io/archive/qt/${QTVERSION_SHORT}/${QTVERSION}/single/qt-everywhere-opensource-src-${QTVERSION}.tar.xz" +RUN bash -c "mkdir -p /qt && cd /qt && wget https://download.qt.io/archive/qt/${QTVERSION_SHORT}/${QTVERSION}/single/qt-everywhere-opensource-src-${QTVERSION}.tar.xz" RUN bash -c "cd /qt && tar xvf qt-everywhere-opensource-src-${QTVERSION}.tar.xz" -RUN bash -c "export MAKEFLAGS=-j$(nproc) && cd /qt/qt-everywhere-opensource-src-${QTVERSION} && ./configure -v -skip qt3d -skip qtconnectivity -skip qtgamepad -skip qtlocation -skip qtcharts -skip qtremoteobjects -skip qtscxml -skip qtsensors -platform linux-g++ -qt-pcre -qt-xcb -qt-xkbcommon-x11 -xkb-config-root /usr/share/X11/xkb -no-pch -nomake tests -nomake examples -confirm-license -opensource -prefix $QTDIR && make -j$(nproc) || make -j 1 install; make -j$(nproc) install && rm -Rf /qt" +RUN bash -c "export MAKEFLAGS=-j$(nproc) && cd /qt/qt-everywhere-opensource-src-${QTVERSION} && ./configure -v -skip qt3d -skip qtconnectivity -skip qtgamepad -skip qtlocation -skip qtcharts -skip qtremoteobjects -skip qtscxml -skip qtsensors -skip qtpurchasing -platform linux-g++ -qt-pcre -qt-xcb -qt-xkbcommon-x11 -xkb-config-root /usr/share/X11/xkb -no-pch -nomake tests -nomake examples -confirm-license -opensource -prefix $QTDIR && make -j$(nproc) || make -j 1 install; make -j$(nproc) install && rm -Rf /qt" RUN ln -sf $QTDIR/bin/qmake /usr/bin/qmake-qt5 # Build qtwebkit -RUN bash -c "mkdir -p /qtwk && cd /qtwk && wget http://download.qt.io/archive/qt/${QTVERSION_SHORT}/5.9.1/submodules/qtwebkit-opensource-src-5.9.1.tar.xz" +RUN bash -c "mkdir -p /qtwk && cd /qtwk && wget https://download.qt.io/archive/qt/${QTVERSION_SHORT}/5.9.1/submodules/qtwebkit-opensource-src-5.9.1.tar.xz" RUN bash -c "cd /qtwk/ && tar xvf qtwebkit-opensource-src-5.9.1.tar.xz" RUN bash -c "cd /qtwk/qtwebkit-opensource-src-5.9.1 && $QTDIR/bin/qmake && make -j$(nproc) || make -j$(nproc) && make -j$(nproc) install && rm -Rf /qtwk" -# Install ninja -RUN bash -c "yum install unzip && mkdir -p /tmp/deploy && cd /tmp/deploy && wget https://github.com/ninja-build/ninja/releases/download/v1.7.2/ninja-linux.zip && unzip ninja-linux.zip && mv -f ninja /usr/local/bin && cd .. && rm -Rf deploy" +# Install ninja (1.8.2 latest working with libc of centos:6.10) +RUN bash -c "yum install unzip && mkdir -p /tmp/deploy && cd /tmp/deploy && wget https://github.com/ninja-build/ninja/releases/download/v1.8.2/ninja-linux.zip && unzip ninja-linux.zip && mv -f ninja /usr/local/bin && cd .. && rm -Rf deploy" # Build Clang/LLVM -RUN bash -c "mkdir -p /llvm && cd /llvm && wget http://llvm.org/releases/${LLVM_VERSION}/llvm-${LLVM_VERSION}.src.tar.xz" +RUN bash -c "mkdir -p /llvm && cd /llvm && wget https://releases.llvm.org/${LLVM_VERSION}/llvm-${LLVM_VERSION}.src.tar.xz" RUN bash -c "cd /llvm && tar xvf llvm-${LLVM_VERSION}.src.tar.xz && cd llvm-${LLVM_VERSION}.src" -RUN bash -c "cd /llvm/llvm-${LLVM_VERSION}.src/tools && wget http://llvm.org/releases/${LLVM_VERSION}/cfe-${LLVM_VERSION}.src.tar.xz" +RUN bash -c "cd /llvm/llvm-${LLVM_VERSION}.src/tools && wget https://llvm.org/releases/${LLVM_VERSION}/cfe-${LLVM_VERSION}.src.tar.xz" RUN bash -c "cd /llvm/llvm-${LLVM_VERSION}.src/tools && tar xvf cfe-${LLVM_VERSION}.src.tar.xz" RUN bash -c ". /opt/rh/python27/enable && . /opt/rh/devtoolset-6/enable && python --version && cd /llvm/llvm-${LLVM_VERSION}.src && mkdir -p build && cd build && cmake3 -G Ninja .. -DCMAKE_INSTALL_PREFIX=/opt/llvm/ -DCMAKE_BUILD_TYPE=Release -DLLVM_INCLUDE_TESTS=OFF -DLLVM_TARGETS_TO_BUILD=X86 && ninja install && rm -Rf /llvm" # Build Python RUN bash -c "mkdir -p /python && cd /python && wget https://www.python.org/ftp/python/$PYTHON_VERSION/Python-$PYTHON_VERSION.tar.xz && tar xvf Python-$PYTHON_VERSION.tar.xz && cd /python/Python-$PYTHON_VERSION && mkdir -p /usr/lib/pkgconfig && ./configure --prefix=/usr --enable-shared && make -j$(nproc) install && rm -Rf /python" CMD /bin/bash diff --git a/appimage/README.md b/appimage/README.md index e005d6d3ee..314b405a8a 100644 --- a/appimage/README.md +++ b/appimage/README.md @@ -1,29 +1,34 @@ To build the AppImage, you basically just have to: -1) Build the base docker container - a) By doing it manually: - ``` - docker pull centos:6.8 - docker build . - docker images (should show the image ID of the newly created image) - docker run - ``` - Once created, you can just detach/attach and start/stop the container, i.e. - ``` - docker start - docker attach - ``` - Building the container will take several hours at least. +1) Build the base docker image - b) **OR**: Get Sven's pre-built version: - ``` - docker pull scummos/centos6.8-qt5.7 - ``` +``` +docker pull centos:6.10 +docker build +``` +Building the image will take several hours at least. -2) Copy the script into the docker container, using docker cp. +2) Create a container from image and open a shell +``` +docker images (should show the image ID of the newly created image) +docker run -t -i --name kdevelopappimagecreator +``` -3) Run the script. - Running the script will also take quite a while the first time you do it, - so it is advisable to always re-use the same container. +3) Copy the script and patches into the docker container +``` +docker cp kdevelop-recipe-centos6.sh kdevelopappimagecreator:/ +for p in *.patch ; do docker cp $p kdevelop5.3appimagecreator:/ ; done +``` -4) Copy the resulting AppImage out of the container using docker cp. +4) Run the script in the interactive shell of the container +``` +./kdevelop-recipe-centos6.sh +``` + +Running the script will also take quite a while the first time you do it, +so it is advisable to always re-use the same container. + +5) Copy the resulting AppImage out of the container +``` +docker cp kdevelopappimagecreator:/out/KDevelop-git-x86_64.AppImage . +``` diff --git a/appimage/kdevelop-recipe-centos6.sh b/appimage/kdevelop-recipe-centos6.sh index 9091f394c6..8f13408b0f 100755 --- a/appimage/kdevelop-recipe-centos6.sh +++ b/appimage/kdevelop-recipe-centos6.sh @@ -1,505 +1,505 @@ #!/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 } SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" QTDIR=/opt/qt5 if [ -z "$KDEVELOP_VERSION" ]; then KDEVELOP_VERSION=5.3 fi if [ -z "$KDEV_PG_QT_VERSION" ]; then - KDEV_PG_QT_VERSION=v2.1.0 + KDEV_PG_QT_VERSION=v2.2.0 fi -KF5_VERSION=v5.51.0 -KDE_PLASMA_VERSION=v5.13.4 # note: need libksysguard commit a0e69617442d720c76da5ebe3323e7a977929db4 (patch which makes plasma dep optional) -KDE_APPLICATION_VERSION=v18.12.1 +KF5_VERSION=v5.54.0 +KDE_PLASMA_BREEZE_VERSION=v5.13.5 # latest version still supporting Qt 5.9 +LIBKSYSGUARD_VERSION=v5.15.5 # latest version still supporting Qt 5.9 +KDE_APPLICATION_VERSION=v19.04.3 GRANTLEE_VERSION=v5.1.0 OKTETA_VERSION=v0.26.2 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 +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 / # Use the new compiler . /opt/rh/devtoolset-6/enable # TODO: Use these vars more export FAKEROOT=/kdevelop.appdir export PREFIX=/kdevelop.appdir/usr/ export SRC=$HOME/src/ export BUILD=$HOME/build 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 # Prepare the install location if [ -z "$SKIP_PRUNE" ]; then 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 fi # start building the deps function build_project { ( PROJECT=$1 VERSION=$2 shift shift # clone if not there mkdir -p $SRC cd $SRC if ( test -d $PROJECT ) then echo "$PROJECT already cloned" cd $PROJECT git stash git reset --hard git fetch git fetch --tags cd .. else if [ -z "$CUSTOM_GIT_URL" ]; then git clone git://anongit.kde.org/$PROJECT else git clone $CUSTOM_GIT_URL fi fi 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 [ ! -z "$PATCH_FILE" ]; then pushd $PROJECT echo "Patching $PROJECT with $PATCH_FILE" git reset --hard patch -p1 < $PATCH_FILE popd fi # create build dir mkdir -p $BUILD/$PROJECT # go there cd $BUILD/$PROJECT # cmake it cmake3 $SRC/$PROJECT -G Ninja -DBUILD_TESTING=OFF -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_INSTALL_PREFIX:PATH=$PREFIX $@ # make ninja # install ninja install ) } function build_framework { ( PROJECT=$1 shift build_project $PROJECT $KF5_VERSION $@ ) } # KDE Frameworks if [ -z "$SKIP_FRAMEWORKS" ]; then build_framework extra-cmake-modules -DBUILD_HTML_DOCS=OFF -DBUILD_MAN_DOCS=OFF 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 (PATCH_FILE=$SCRIPT_DIR/knotifications_no_phonon.patch build_framework knotifications) build_framework knotifyconfig build_framework kdoctools build_framework breeze-icons -DBINARY_ICONS_RESOURCE=1 build_framework kpty build_framework kinit fi # KDE Plasma -build_project libksysguard $KDE_PLASMA_VERSION -build_project kdecoration $KDE_PLASMA_VERSION # for breeze -build_project breeze $KDE_PLASMA_VERSION +build_project libksysguard $LIBKSYSGUARD_VERSION +build_project kdecoration $KDE_PLASMA_BREEZE_VERSION # needed by breeze +build_project breeze $KDE_PLASMA_BREEZE_VERSION # KDE Applications build_project libkomparediff2 $KDE_APPLICATION_VERSION -build_project kate $KDE_APPLICATION_VERSION # for snippet plugin, see T3826 +build_project kate $KDE_APPLICATION_VERSION -DDISABLE_ALL_OPTIONAL_SUBDIRECTORIES=TRUE -DBUILD_addons=TRUE -DBUILD_snippets=TRUE -DBUILD_kate-ctags=TRUE # for snippet plugin, see T3826 build_project konsole $KDE_APPLICATION_VERSION build_project okteta $OKTETA_VERSION -DBUILD_DESIGNERPLUGIN=OFF -DBUILD_OKTETAKASTENLIBS=OFF # Extra (CUSTOM_GIT_URL=https://github.com/steveire/grantlee.git PATCH_FILE=$SCRIPT_DIR/grantlee_avoid_recompilation.patch build_project grantlee $GRANTLEE_VERSION) # KDevelop build_project kdevelop-pg-qt $KDEV_PG_QT_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 # Install some colorschemes cd $BUILD $SRC/kdevelop/release-scripts/install_colorschemes.py /kdevelop.appdir/usr/share 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/ PLUGINS=$($QTDIR/bin/qmake -query QT_INSTALL_PLUGINS) echo "Using plugin dir: $PLUGINS" 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/sqldrivers ./usr/lib/qt5/plugins/ # qsqlite is required for the Welcome Page plugin and for QtHelp cp -r $PLUGINS/xcbglintegrations ./usr/lib/qt5/plugins/ mkdir -p ./usr/lib/qt5/qml QML_DIR=$QTDIR/qml # for the Welcome Page plugin cp -r $QML_DIR/QtQuick ./usr/lib/qml cp -r $QML_DIR/QtQuick.2 ./usr/lib/qml cp -R /kdevelop.appdir/usr/lib/grantlee/ /kdevelop.appdir/usr/lib/qt5/plugins/ rm -Rf /kdevelop.appdir/usr/lib/grantlee mkdir -p /kdevelop.appdir/$LLVM_ROOT/lib/ cp -r $LLVM_ROOT/lib/clang /kdevelop.appdir/$LLVM_ROOT/lib 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/ ldd usr/bin/kdevelop | 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/libfreetype.so.6 || 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 # see https://github.com/AppImage/AppImageKit/issues/629#issuecomment-359013844 -- we assume this to be present on all systems 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/mkspecs || true rm -rf usr/share/ECM/ || true rm -rf usr/share/gettext || true rm -rf usr/share/pkgconfig || true rm -rf usr/etc/xdg/*.categories || true strip -g $(find usr/bin usr/lib -type f) || 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 # remove unused registration data rm -rf ./usr/share/applications/ || true # remove all appdata besides kdevelop one rm -f ./usr/share/metainfo/org.kde.{breezedark.desktop,kate,kwrite,konsole}.appdata.xml rm -f ./usr/share/metainfo/org.kde.kdev-{php,python}.metainfo.xml 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_CLANG_BUILTIN_DIR=\$DIR/opt/llvm/lib/clang/6.0.1/include +export KDEV_CLANG_BUILTIN_DIR=\$DIR/opt/llvm/lib/clang/8.0.0/include export KDEV_DISABLE_PLUGINS=KDevWelcomePage cd \$HOME kdevelop \$@ EOF chmod +x AppRun # use normal desktop file, but remove actions, not yet handled by appimaged & Co cp $SRC/kdevelop/app/org.kde.kdevelop.desktop org.kde.kdevelop.desktop sed -i -e '/^Actions=/d;/^\[Desktop Action /Q' org.kde.kdevelop.desktop cp $SRC/kdevelop/app/icons/256-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/ # Breeze cruft cp $BUILD/breeze-icons/icons/breeze-icons.rcc /kdevelop.appdir/usr/share/kdevelop/icontheme.rcc rm -Rf /kdevelop.appdir/usr/share/icons/{B,b}reeze* # not needed because of the rcc rm -Rf /kdevelop.appdir/usr/share/wallpapers rm -Rf /kdevelop.appdir/usr/share/plasma 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 {} \; echo "Final listing of files which will end up in the AppImage:" find /kdevelop.appdir 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 # Get appimagetool mkdir -p $SRC/appimagetool pushd $SRC/appimagetool wget -c -O appimagetool https://github.com/AppImage/AppImageKit/releases/download/11/appimagetool-x86_64.AppImage chmod +x ./appimagetool ./appimagetool --appimage-extract # no fuse on this docker instance... export PATH=$PWD/squashfs-root/usr/bin:$PATH popd mkdir -p /out rm -f /out/*.AppImage || true appimagetool /kdevelop.appdir/ /out/$APPIMAGE chmod a+rwx /out/$APPIMAGE # So that we can edit the AppImage outside of the Docker container diff --git a/appimage/knotifications_no_phonon.patch b/appimage/knotifications_no_phonon.patch index 762ec1195e..4851bcbdad 100644 --- a/appimage/knotifications_no_phonon.patch +++ b/appimage/knotifications_no_phonon.patch @@ -1,14 +1,13 @@ diff --git a/CMakeLists.txt b/CMakeLists.txt -index aa2926f..60f4277 100644 +index da34e23..87fef63 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt -@@ -77,7 +77,7 @@ else() - set_package_properties(Phonon4Qt5 PROPERTIES - DESCRIPTION "Qt-based audio library" - # This is REQUIRED since you cannot tell CMake "either one of those two optional ones are required" -- TYPE REQUIRED -+ TYPE OPTIONAL +@@ -86,7 +86,7 @@ else() PURPOSE "Needed to build audio notification support when Canberra isn't available") + if (NOT ANDROID) + # This is REQUIRED since you cannot tell CMake "either one of those two optional ones are required" +- set_package_properties(Phonon4Qt5 PROPERTIES TYPE REQUIRED) ++# set_package_properties(Phonon4Qt5 PROPERTIES TYPE REQUIRED) + endif() if (Phonon4Qt5_FOUND) add_definitions(-DHAVE_PHONON4QT5) - diff --git a/kdevplatform/language/util/debuglanguageparserhelper.h b/kdevplatform/language/util/debuglanguageparserhelper.h index 50bf834acf..75d018b481 100644 --- a/kdevplatform/language/util/debuglanguageparserhelper.h +++ b/kdevplatform/language/util/debuglanguageparserhelper.h @@ -1,246 +1,246 @@ /* This file is part of KDevelop Copyright 2010 Milian Wolff Copyright 2014 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 KDEVPLATFORM_DEBUGLANGUAGEPARSERHELPER_H #define KDEVPLATFORM_DEBUGLANGUAGEPARSERHELPER_H #include #include #ifndef Q_OS_WIN #include // for isatty #endif #include #include #include #include #include #include #include #include #include #include #include namespace KDevelopUtils { QTextStream qout(stdout); QTextStream qerr(stderr); QTextStream qin(stdin); using TokenTextFunc = QString (*)(int); /** * This class is a pure helper to use for binaries that you can * run on short snippets of test code or whole files and let * it print the generated tokens or AST. * * It should work fine for any KDevelop-PG-Qt based parser. * * * @tparam SessionT the parse session for your language. * @tparam TokenStreamT the token stream for your language, based on KDevPG::TokenStreamBase. * @tparam TokenT the token class for your language, based on KDevPG::Token. * @tparam LexerT the Lexer for your language. * @tparam StartAstT the AST node that is returned from @c SessionT::parse(). * @tparam DebugVisitorT the debug visitor for your language. * @tparam TokenTextT function pointer to the function that returns a string representation for an integral token. */ template class DebugLanguageParserHelper { public: DebugLanguageParserHelper(const bool printAst, const bool printTokens) : m_printAst(printAst) , m_printTokens(printTokens) { m_session.setDebug(printAst); } /// parse contents of a file void parseFile(const QString& fileName) { if (!m_session.readFile(fileName, "utf-8")) { qerr << "Can't open file " << fileName << endl; std::exit(255); } else { qout << "Parsing file " << fileName << endl; } runSession(); } /// parse code directly void parseCode(const QString& code) { m_session.setContents(code); qout << "Parsing input" << endl; runSession(); } private: /** * actually run the parse session */ void runSession() { if (m_printTokens) { TokenStreamT tokenStream; LexerT lexer(&tokenStream, m_session.contents()); int token; while ((token = lexer.nextTokenKind())) { TokenT& t = tokenStream.push(); t.begin = lexer.tokenBegin(); t.end = lexer.tokenEnd(); t.kind = token; printToken(token, lexer); } printToken(token, lexer); if (tokenStream.size() > 0) { qint64 line; qint64 column; tokenStream.endPosition(tokenStream.size() - 1, &line, &column); qDebug() << "last token endPosition: line" << line << "column" << column; } else { qDebug() << "empty token stream"; } } StartAstT* ast = 0; if (!m_session.parse(&ast)) { qerr << "no AST tree could be generated" << endl; } else { qout << "AST tree successfully generated" << endl; if (m_printAst) { DebugVisitorT debugVisitor(m_session.tokenStream(), m_session.contents()); debugVisitor.visitStart(ast); } } const auto problems = m_session.problems(); if (!problems.isEmpty()) { qout << endl << "problems encountered during parsing:" << endl; for (auto& p : problems) { qout << p->description() << endl; } } else { qout << "no problems encountered during parsing" << endl; } if (!ast) { exit(255); } } void printToken(int token, const LexerT& lexer) const { int begin = lexer.tokenBegin(); int end = lexer.tokenEnd(); qout << m_session.contents().mid(begin, end - begin + 1).replace('\n', "\\n") << ' ' << TokenTextT(token) << endl; } SessionT m_session; const bool m_printAst; const bool m_printTokens; }; template void setupCustomArgs(QCommandLineParser* parser) { Q_UNUSED(parser); } template void setCustomArgs(ParserT* parser, QCommandLineParser* commandLineParser) { Q_UNUSED(parser); Q_UNUSED(commandLineParser); } /// call this after setting up @p aboutData in your @c main() function. template int initAndRunParser(KAboutData& aboutData, int argc, char* argv[]) { qout.setCodec("UTF-8"); qerr.setCodec("UTF-8"); qin.setCodec("UTF-8"); QApplication app(argc, argv); KAboutData::setApplicationData(aboutData); QCommandLineParser parser; aboutData.setupCommandLine(&parser); parser.addPositionalArgument("files", i18n( "files or - to read from STDIN, the latter is the default if nothing is provided"), "[FILE...]"); parser.addOption(QCommandLineOption{QStringList{"a", "print-ast"}, i18n("print generated AST tree")}); parser.addOption(QCommandLineOption{QStringList{"t", "print-tokens"}, i18n("print generated token stream")}); parser.addOption(QCommandLineOption{QStringList{"c", "code"}, i18n("code to parse"), "code"}); setupCustomArgs(&parser); parser.process(app); aboutData.processCommandLine(&parser); QStringList files = parser.positionalArguments(); bool printAst = parser.isSet("print-ast"); bool printTokens = parser.isSet("print-tokens"); KDevelop::AutoTestShell::init(); KDevelop::TestCore::initialize(KDevelop::Core::NoUi); KDevelop::DUChain::self()->disablePersistentStorage(); KDevelop::CodeRepresentation::setDiskChangesForbidden(true); ParserT parserT(printAst, printTokens); setCustomArgs(&parserT, &parser); if (parser.isSet("code")) { parserT.parseCode(parser.value("code")); } else if (files.isEmpty()) { files << "-"; } for (const QString& fileName : qAsConst(files)) { if (fileName == "-") { #ifndef Q_OS_WIN if (isatty(STDIN_FILENO)) { qerr << "no STDIN given" << endl; return 255; } #endif parserT.parseCode(qin.readAll().toUtf8()); } else { - parserT.parseFile(fileName); + parserT.parseFile(QFileInfo(fileName).absoluteFilePath()); } } KDevelop::TestCore::shutdown(); return 0; } } #endif // KDEVPLATFORM_DEBUGLANGUAGEPARSERHELPER_H diff --git a/kdevplatform/shell/plugincontroller.cpp b/kdevplatform/shell/plugincontroller.cpp index c465b387a8..64f997d58a 100644 --- a/kdevplatform/shell/plugincontroller.cpp +++ b/kdevplatform/shell/plugincontroller.cpp @@ -1,861 +1,868 @@ /* This file is part of the KDE project Copyright 2004, 2007 Alexander Dymo Copyright 2006 Matt Rogers Based on code from Kopete Copyright (c) 2002-2003 Martijn Klingens 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 "plugincontroller.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "core.h" #include "shellextension.h" #include "runcontroller.h" #include "debugcontroller.h" #include "documentationcontroller.h" #include "sourceformattercontroller.h" #include "projectcontroller.h" #include "ktexteditorpluginintegration.h" #include "debug.h" namespace { inline QString KEY_Plugins() { return QStringLiteral("Plugins"); } inline QString KEY_Suffix_Enabled() { return QStringLiteral("Enabled"); } inline QString KEY_LoadMode() { return QStringLiteral("X-KDevelop-LoadMode"); } inline QString KEY_Category() { return QStringLiteral("X-KDevelop-Category"); } inline QString KEY_Mode() { return QStringLiteral("X-KDevelop-Mode"); } inline QString KEY_Version() { return QStringLiteral("X-KDevelop-Version"); } inline QString KEY_Interfaces() { return QStringLiteral("X-KDevelop-Interfaces"); } inline QString KEY_Required() { return QStringLiteral("X-KDevelop-IRequired"); } inline QString KEY_Optional() { return QStringLiteral("X-KDevelop-IOptional"); } inline QString KEY_KPlugin() { return QStringLiteral("KPlugin"); } inline QString KEY_EnabledByDefault() { return QStringLiteral("EnabledByDefault"); } inline QString KEY_Global() { return QStringLiteral("Global"); } inline QString KEY_Project() { return QStringLiteral("Project"); } inline QString KEY_Gui() { return QStringLiteral("GUI"); } inline QString KEY_AlwaysOn() { return QStringLiteral("AlwaysOn"); } inline QString KEY_UserSelectable() { return QStringLiteral("UserSelectable"); } bool isUserSelectable( const KPluginMetaData& info ) { QString loadMode = info.value(KEY_LoadMode()); return loadMode.isEmpty() || loadMode == KEY_UserSelectable(); } bool isGlobalPlugin( const KPluginMetaData& info ) { return info.value(KEY_Category()) == KEY_Global(); } bool hasMandatoryProperties( const KPluginMetaData& info ) { QString mode = info.value(KEY_Mode()); if (mode.isEmpty()) { return false; } // when the plugin is installed into the versioned plugin path, it's good to go if (info.fileName().contains(QLatin1String("/kdevplatform/" QT_STRINGIFY(KDEVELOP_PLUGIN_VERSION) "/"))) { return true; } // the version property is only required when the plugin is not installed into the right directory QVariant version = info.rawData().value(KEY_Version()).toVariant(); if (version.isValid() && version.value() == KDEVELOP_PLUGIN_VERSION) { return true; } return false; } bool constraintsMatch( const KPluginMetaData& info, const QVariantMap& constraints) { for (auto it = constraints.begin(); it != constraints.end(); ++it) { const auto property = info.rawData().value(it.key()).toVariant(); if (!property.isValid()) { return false; } else if (property.canConvert()) { QSet values = property.toStringList().toSet(); QSet expected = it.value().toStringList().toSet(); if (!values.contains(expected)) { return false; } } else if (it.value() != property) { return false; } } return true; } struct Dependency { explicit Dependency(const QString &dependency) : interface(dependency) { if (dependency.contains(QLatin1Char('@'))) { const auto list = dependency.split(QLatin1Char('@'), QString::SkipEmptyParts); if (list.size() == 2) { interface = list.at(0); pluginName = list.at(1); } } } QString interface; QString pluginName; }; } namespace KDevelop { class PluginControllerPrivate { public: explicit PluginControllerPrivate(Core *core) : core(core) {} QVector plugins; //map plugin infos to currently loaded plugins using InfoToPluginMap = QHash; InfoToPluginMap loadedPlugins; // The plugin manager's mode. The mode is StartingUp until loadAllPlugins() // has finished loading the plugins, after which it is set to Running. // ShuttingDown and DoneShutdown are used during shutdown by the // async unloading of plugins. enum CleanupMode { Running /**< the plugin manager is running */, CleaningUp /**< the plugin manager is cleaning up for shutdown */, CleanupDone /**< the plugin manager has finished cleaning up */ }; CleanupMode cleanupMode; bool canUnload(const KPluginMetaData& plugin) { qCDebug(SHELL) << "checking can unload for:" << plugin.name() << plugin.value(KEY_LoadMode()); if (plugin.value(KEY_LoadMode()) == KEY_AlwaysOn()) { return false; } const QStringList interfaces = KPluginMetaData::readStringList(plugin.rawData(), KEY_Interfaces()); qCDebug(SHELL) << "checking dependencies:" << interfaces; for (auto it = loadedPlugins.constBegin(), end = loadedPlugins.constEnd(); it != end; ++it) { const KPluginMetaData& info = it.key(); if (info.pluginId() != plugin.pluginId()) { const QStringList dependencies = KPluginMetaData::readStringList(plugin.rawData(), KEY_Required()) + KPluginMetaData::readStringList(plugin.rawData(), KEY_Optional()); for (const QString& dep : dependencies) { Dependency dependency(dep); if (!dependency.pluginName.isEmpty() && dependency.pluginName != plugin.pluginId()) { continue; } if (interfaces.contains(dependency.interface) && !canUnload(info)) { return false; } } } } return true; } KPluginMetaData infoForId( const QString& id ) const { for (const KPluginMetaData& info : plugins) { if (info.pluginId() == id) { return info; } } return KPluginMetaData(); } /** * Iterate over all cached plugin infos, and call the functor for every enabled plugin. * * If an extension and/or pluginName is given, the functor will only be called for * those plugins matching this information. * * The functor should return false when the iteration can be stopped, and true if it * should be continued. */ template void foreachEnabledPlugin(F func, const QString &extension = {}, const QVariantMap& constraints = QVariantMap(), const QString &pluginName = {}) const { const auto currentPlugins = plugins; for (const auto& info : currentPlugins) { if ((pluginName.isEmpty() || info.pluginId() == pluginName) && (extension.isEmpty() || KPluginMetaData::readStringList(info.rawData(), KEY_Interfaces()).contains(extension)) && constraintsMatch(info, constraints) && isEnabled(info)) { if (!func(info)) { break; } } } } enum EnableState { DisabledByEnv, DisabledBySetting, DisabledByUnknown, FirstEnabledState, EnabledBySetting = FirstEnabledState, AlwaysEnabled }; /** * Estimate enabled state of a plugin */ EnableState enabledState(const KPluginMetaData& info) const { // first check black listing from environment static const QStringList disabledPlugins = QString::fromLatin1(qgetenv("KDEV_DISABLE_PLUGINS")).split(QLatin1Char(';')); if (disabledPlugins.contains(info.pluginId())) { return DisabledByEnv; } if (!isUserSelectable( info )) return AlwaysEnabled; // read stored user preference const KConfigGroup grp = Core::self()->activeSession()->config()->group( KEY_Plugins() ); const QString pluginEnabledKey = info.pluginId() + KEY_Suffix_Enabled(); if (grp.hasKey(pluginEnabledKey)) { return grp.readEntry(pluginEnabledKey, true) ? EnabledBySetting : DisabledBySetting; } // should not happen return DisabledByUnknown; } /** * Decide whether a plugin is enabled */ bool isEnabled(const KPluginMetaData& info) const { return (enabledState(info) >= FirstEnabledState); } Core* const core; }; PluginController::PluginController(Core *core) : IPluginController() , d_ptr(new PluginControllerPrivate(core)) { Q_D(PluginController); setObjectName(QStringLiteral("PluginController")); QSet foundPlugins; auto newPlugins = KPluginLoader::findPlugins(QStringLiteral("kdevplatform/" QT_STRINGIFY(KDEVELOP_PLUGIN_VERSION)), [&](const KPluginMetaData& meta) { if (meta.serviceTypes().contains(QStringLiteral("KDevelop/Plugin"))) { foundPlugins.insert(meta.pluginId()); return true; } else { qCWarning(SHELL) << "Plugin" << meta.fileName() << "is installed into the kdevplatform plugin directory, but does not have" " \"KDevelop/Plugin\" set as the service type. This plugin will not be loaded."; return false; } }); qCDebug(SHELL) << "Found" << newPlugins.size() << "plugins:" << foundPlugins; if (newPlugins.isEmpty()) { qCWarning(SHELL) << "Did not find any plugins, check your environment."; qCWarning(SHELL) << " Note: QT_PLUGIN_PATH is set to:" << qgetenv("QT_PLUGIN_PATH"); } d->plugins = newPlugins; KTextEditorIntegration::initialize(); const QVector ktePlugins = KPluginLoader::findPlugins(QStringLiteral("ktexteditor"), [](const KPluginMetaData & md) { return md.serviceTypes().contains(QStringLiteral("KTextEditor/Plugin")) && md.serviceTypes().contains(QStringLiteral("KDevelop/Plugin")); }); foundPlugins.clear(); std::for_each(ktePlugins.cbegin(), ktePlugins.cend(), [&foundPlugins](const KPluginMetaData& data) { foundPlugins << data.pluginId(); }); qCDebug(SHELL) << "Found" << ktePlugins.size() << " KTextEditor plugins:" << foundPlugins; d->plugins.reserve(d->plugins.size() + ktePlugins.size()); for (const auto& info : ktePlugins) { auto data = info.rawData(); + // temporary workaround for Kate's ctags plugin being enabled by default + // see https://mail.kde.org/pipermail/kwrite-devel/2019-July/004821.html + if (info.pluginId() == QLatin1String("katectagsplugin")) { + auto kpluginData = data[KEY_KPlugin()].toObject(); + kpluginData[KEY_EnabledByDefault()] = false; + data[KEY_KPlugin()] = kpluginData; + } // add some KDevelop specific JSON data data[KEY_Category()] = KEY_Global(); data[KEY_Mode()] = KEY_Gui(); data[KEY_Version()] = KDEVELOP_PLUGIN_VERSION; d->plugins.append({data, info.fileName(), info.metaDataFileName()}); } d->cleanupMode = PluginControllerPrivate::Running; // Register the KDevelop::IPlugin* metatype so we can properly unload it qRegisterMetaType( "KDevelop::IPlugin*" ); } PluginController::~PluginController() { Q_D(PluginController); if ( d->cleanupMode != PluginControllerPrivate::CleanupDone ) { qCWarning(SHELL) << "Destructing plugin controller without going through the shutdown process!"; } } KPluginMetaData PluginController::pluginInfo( const IPlugin* plugin ) const { Q_D(const PluginController); return d->loadedPlugins.key(const_cast(plugin)); } void PluginController::cleanup() { Q_D(PluginController); if(d->cleanupMode != PluginControllerPrivate::Running) { //qCDebug(SHELL) << "called when not running. state =" << d->cleanupMode; return; } d->cleanupMode = PluginControllerPrivate::CleaningUp; // Ask all plugins to unload while ( !d->loadedPlugins.isEmpty() ) { //Let the plugin do some stuff before unloading unloadPlugin(d->loadedPlugins.begin().value(), Now); } d->cleanupMode = PluginControllerPrivate::CleanupDone; } IPlugin* PluginController::loadPlugin( const QString& pluginName ) { return loadPluginInternal( pluginName ); } void PluginController::initialize() { Q_D(PluginController); QElapsedTimer timer; timer.start(); QMap pluginMap; if( ShellExtension::getInstance()->defaultPlugins().isEmpty() ) { for (const KPluginMetaData& pi : qAsConst(d->plugins)) { QJsonValue enabledByDefaultValue = pi.rawData()[KEY_KPlugin()].toObject()[KEY_EnabledByDefault()]; // plugins enabled until explicitly specified otherwise const bool enabledByDefault = (enabledByDefaultValue.isNull() || enabledByDefaultValue.toBool()); pluginMap.insert(pi.pluginId(), enabledByDefault); } } else { // Get the default from the ShellExtension const auto defaultPlugins = ShellExtension::getInstance()->defaultPlugins(); for (const QString& s : defaultPlugins) { pluginMap.insert( s, true ); } } KConfigGroup grp = Core::self()->activeSession()->config()->group( KEY_Plugins() ); QMap entries = grp.entryMap(); QMap::Iterator it; for ( it = entries.begin(); it != entries.end(); ++it ) { const QString key = it.key(); if (key.endsWith(KEY_Suffix_Enabled())) { const QString pluginid = key.left(key.length() - 7); const bool defValue = pluginMap.value( pluginid, false ); const bool enabled = grp.readEntry(key, defValue); pluginMap.insert( pluginid, enabled ); } } // store current known set of enabled plugins for (const KPluginMetaData& pi : qAsConst(d->plugins)) { if (isUserSelectable(pi)) { auto it = pluginMap.constFind(pi.pluginId()); if (it != pluginMap.constEnd() && (it.value())) { grp.writeEntry(pi.pluginId() + KEY_Suffix_Enabled(), true); } } else { // Backward compat: Remove any now-obsolete entries grp.deleteEntry(pi.pluginId() + QLatin1String("Disabled")); } } // Synchronize so we're writing out to the file. grp.sync(); // load global plugins for (const KPluginMetaData& pi : qAsConst(d->plugins)) { if (isGlobalPlugin(pi)) { loadPluginInternal(pi.pluginId()); } } qCDebug(SHELL) << "Done loading plugins - took:" << timer.elapsed() << "ms"; } QList PluginController::loadedPlugins() const { Q_D(const PluginController); return d->loadedPlugins.values(); } bool PluginController::unloadPlugin( const QString & pluginId ) { Q_D(PluginController); IPlugin *thePlugin = plugin( pluginId ); bool canUnload = d->canUnload( d->infoForId( pluginId ) ); qCDebug(SHELL) << "Unloading plugin:" << pluginId << "?" << thePlugin << canUnload; if( thePlugin && canUnload ) { return unloadPlugin(thePlugin, Later); } return (canUnload && thePlugin); } bool PluginController::unloadPlugin(IPlugin* plugin, PluginDeletion deletion) { Q_D(PluginController); qCDebug(SHELL) << "unloading plugin:" << plugin << pluginInfo( plugin ).name(); emit unloadingPlugin(plugin); plugin->unload(); emit pluginUnloaded(plugin); //Remove the plugin from our list of plugins so we create a new //instance when we're asked for it again. //This is important to do right here, not later when the plugin really //vanishes. For example project re-opening might try to reload the plugin //and then would get the "old" pointer which will be deleted in the next //event loop run and thus causing crashes. for ( PluginControllerPrivate::InfoToPluginMap::Iterator it = d->loadedPlugins.begin(); it != d->loadedPlugins.end(); ++it ) { if ( it.value() == plugin ) { d->loadedPlugins.erase( it ); break; } } if (deletion == Later) plugin->deleteLater(); else delete plugin; return true; } KPluginMetaData PluginController::infoForPluginId( const QString &pluginId ) const { Q_D(const PluginController); auto it = std::find_if(d->plugins.constBegin(), d->plugins.constEnd(), [&](const KPluginMetaData& info) { return (info.pluginId() == pluginId); }); return (it != d->plugins.constEnd()) ? *it : KPluginMetaData(); } IPlugin *PluginController::loadPluginInternal( const QString &pluginId ) { Q_D(PluginController); QElapsedTimer timer; timer.start(); KPluginMetaData info = infoForPluginId( pluginId ); if ( !info.isValid() ) { qCWarning(SHELL) << "Unable to find a plugin named '" << pluginId << "'!" ; return nullptr; } if ( IPlugin* plugin = d->loadedPlugins.value( info ) ) { return plugin; } const auto enabledState = d->enabledState(info); if (enabledState < PluginControllerPrivate::FirstEnabledState) { // Do not load disabled plugins qCDebug(SHELL) << "Not loading plugin named" << pluginId << ( (enabledState == PluginControllerPrivate::DisabledByEnv) ? "because disabled by KDEV_DISABLE_PLUGINS." : (enabledState == PluginControllerPrivate::DisabledBySetting) ? "because disabled by setting." : /* else, should not happen */ "because disabled for unknown reason."); return nullptr; } if ( !hasMandatoryProperties( info ) ) { qCWarning(SHELL) << "Unable to load plugin named" << pluginId << "because not all mandatory properties are set."; return nullptr; } if ( info.value(KEY_Mode()) == KEY_Gui() && Core::self()->setupFlags() == Core::NoUi ) { qCDebug(SHELL) << "Not loading plugin named" << pluginId << "- Running in No-Ui mode, but the plugin says it needs a GUI"; return nullptr; } qCDebug(SHELL) << "Attempting to load" << pluginId << "- name:" << info.name(); emit loadingPlugin( info.pluginId() ); // first, ensure all dependencies are available and not disabled // this is unrelated to whether they are loaded already or not. // when we depend on e.g. A and B, but B cannot be found, then we // do not want to load A first and then fail on B and leave A loaded. // this would happen if we'd skip this step here and directly loadDependencies. QStringList missingInterfaces; if ( !hasUnresolvedDependencies( info, missingInterfaces ) ) { qCWarning(SHELL) << "Can't load plugin" << pluginId << "some of its required dependencies could not be fulfilled:" << missingInterfaces.join(QLatin1Char(',')); return nullptr; } // now ensure all dependencies are loaded QString failedDependency; if( !loadDependencies( info, failedDependency ) ) { qCWarning(SHELL) << "Can't load plugin" << pluginId << "because a required dependency could not be loaded:" << failedDependency; return nullptr; } // same for optional dependencies, but don't error out if anything fails loadOptionalDependencies( info ); // now we can finally load the plugin itself KPluginLoader loader(info.fileName()); auto factory = loader.factory(); if (!factory) { qCWarning(SHELL) << "Can't load plugin" << pluginId << "because a factory to load the plugin could not be obtained:" << loader.errorString(); return nullptr; } // now create it auto plugin = factory->create(d->core); if (!plugin) { if (auto katePlugin = factory->create(d->core, QVariantList() << info.pluginId())) { plugin = new KTextEditorIntegration::Plugin(katePlugin, d->core); } else { qCWarning(SHELL) << "Creating plugin" << pluginId << "failed."; return nullptr; } } KConfigGroup group = Core::self()->activeSession()->config()->group(KEY_Plugins()); // runtime errors such as missing executables on the system or such get checked now if (plugin->hasError()) { qCWarning(SHELL) << "Could not load plugin" << pluginId << ", it reported the error:" << plugin->errorDescription() << "Disabling the plugin now."; group.writeEntry(info.pluginId() + KEY_Suffix_Enabled(), false); // do the same as KPluginInfo did group.sync(); unloadPlugin(pluginId); return nullptr; } // yay, it all worked - the plugin is loaded d->loadedPlugins.insert(info, plugin); group.writeEntry(info.pluginId() + KEY_Suffix_Enabled(), true); // do the same as KPluginInfo did group.sync(); qCDebug(SHELL) << "Successfully loaded plugin" << pluginId << "from" << loader.fileName() << "- took:" << timer.elapsed() << "ms"; emit pluginLoaded( plugin ); return plugin; } IPlugin* PluginController::plugin(const QString& pluginId) const { Q_D(const PluginController); KPluginMetaData info = infoForPluginId( pluginId ); if ( !info.isValid() ) return nullptr; return d->loadedPlugins.value( info ); } bool PluginController::hasUnresolvedDependencies( const KPluginMetaData& info, QStringList& missing ) const { Q_D(const PluginController); QSet required = KPluginMetaData::readStringList(info.rawData(), KEY_Required()).toSet(); if (!required.isEmpty()) { d->foreachEnabledPlugin([&required] (const KPluginMetaData& plugin) -> bool { const auto interfaces = KPluginMetaData::readStringList(plugin.rawData(), KEY_Interfaces()); for (const QString& iface : interfaces) { required.remove(iface); required.remove(iface + QLatin1Char('@') + plugin.pluginId()); } return !required.isEmpty(); }); } // if we found all dependencies required should be empty now if (!required.isEmpty()) { missing = required.toList(); return false; } return true; } void PluginController::loadOptionalDependencies( const KPluginMetaData& info ) { const QStringList dependencies = KPluginMetaData::readStringList(info.rawData(), KEY_Optional()); for (const QString& dep : dependencies) { Dependency dependency(dep); if (!pluginForExtension(dependency.interface, dependency.pluginName)) { qCDebug(SHELL) << "Couldn't load optional dependency:" << dep << info.pluginId(); } } } bool PluginController::loadDependencies( const KPluginMetaData& info, QString& failedDependency ) { const QStringList dependencies = KPluginMetaData::readStringList(info.rawData(), KEY_Required()); for (const QString& value : dependencies) { Dependency dependency(value); if (!pluginForExtension(dependency.interface, dependency.pluginName)) { failedDependency = value; return false; } } return true; } IPlugin *PluginController::pluginForExtension(const QString &extension, const QString &pluginName, const QVariantMap& constraints) { Q_D(PluginController); IPlugin* plugin = nullptr; d->foreachEnabledPlugin([this, &plugin] (const KPluginMetaData& info) -> bool { Q_D(PluginController); plugin = d->loadedPlugins.value( info ); if( !plugin ) { plugin = loadPluginInternal( info.pluginId() ); } return !plugin; }, extension, constraints, pluginName); return plugin; } QList PluginController::allPluginsForExtension(const QString &extension, const QVariantMap& constraints) { Q_D(PluginController); //qCDebug(SHELL) << "Finding all Plugins for Extension:" << extension << "|" << constraints; QList plugins; d->foreachEnabledPlugin([this, &plugins] (const KPluginMetaData& info) -> bool { Q_D(PluginController); IPlugin* plugin = d->loadedPlugins.value( info ); if( !plugin) { plugin = loadPluginInternal( info.pluginId() ); } if (plugin && !plugins.contains(plugin)) { plugins << plugin; } return true; }, extension, constraints); return plugins; } QVector PluginController::queryExtensionPlugins(const QString& extension, const QVariantMap& constraints) const { Q_D(const PluginController); QVector plugins; d->foreachEnabledPlugin([&plugins] (const KPluginMetaData& info) -> bool { plugins << info; return true; }, extension, constraints); return plugins; } QStringList PluginController::allPluginNames() { Q_D(PluginController); QStringList names; names.reserve(d->plugins.size()); for (const KPluginMetaData& info : qAsConst(d->plugins)) { names << info.pluginId(); } return names; } QList PluginController::queryPluginsForContextMenuExtensions(KDevelop::Context* context, QWidget* parent) const { Q_D(const PluginController); // This fixes random order of extension menu items between different runs of KDevelop. // Without sorting we have random reordering of "Analyze With" submenu for example: // 1) "Cppcheck" actions, "Vera++" actions - first run // 2) "Vera++" actions, "Cppcheck" actions - some other run. QMultiMap sortedPlugins; for (auto it = d->loadedPlugins.constBegin(); it != d->loadedPlugins.constEnd(); ++it) { sortedPlugins.insert(it.key().name(), it.value()); } QList exts; exts.reserve(sortedPlugins.size()); for (IPlugin* plugin : qAsConst(sortedPlugins)) { exts << plugin->contextMenuExtension(context, parent); } exts << Core::self()->debugControllerInternal()->contextMenuExtension(context, parent); exts << Core::self()->documentationControllerInternal()->contextMenuExtension(context, parent); exts << Core::self()->sourceFormatterControllerInternal()->contextMenuExtension(context, parent); exts << Core::self()->runControllerInternal()->contextMenuExtension(context, parent); exts << Core::self()->projectControllerInternal()->contextMenuExtension(context, parent); return exts; } QStringList PluginController::projectPlugins() const { Q_D(const PluginController); QStringList names; for (const KPluginMetaData& info : qAsConst(d->plugins)) { if (info.value(KEY_Category()) == KEY_Project()) { names << info.pluginId(); } } return names; } void PluginController::loadProjectPlugins() { const auto pluginNames = projectPlugins(); for (const QString& name : pluginNames) { loadPluginInternal( name ); } } void PluginController::unloadProjectPlugins() { const auto pluginNames = projectPlugins(); for (const QString& name : pluginNames) { unloadPlugin( name ); } } QVector PluginController::allPluginInfos() const { Q_D(const PluginController); return d->plugins; } void PluginController::updateLoadedPlugins() { Q_D(PluginController); QStringList defaultPlugins = ShellExtension::getInstance()->defaultPlugins(); KConfigGroup grp = Core::self()->activeSession()->config()->group( KEY_Plugins() ); for (const KPluginMetaData& info : qAsConst(d->plugins)) { if( isGlobalPlugin( info ) ) { bool enabled = grp.readEntry(info.pluginId() + KEY_Suffix_Enabled(), ( defaultPlugins.isEmpty() || defaultPlugins.contains( info.pluginId() ) ) ) || !isUserSelectable( info ); bool loaded = d->loadedPlugins.contains( info ); if( loaded && !enabled ) { qCDebug(SHELL) << "unloading" << info.pluginId(); if( !unloadPlugin( info.pluginId() ) ) { grp.writeEntry( info.pluginId() + KEY_Suffix_Enabled(), false ); } } else if( !loaded && enabled ) { loadPluginInternal( info.pluginId() ); } } // TODO: what about project plugins? what about dependency plugins? } } void PluginController::resetToDefaults() { Q_D(PluginController); KSharedConfigPtr cfg = Core::self()->activeSession()->config(); cfg->deleteGroup( KEY_Plugins() ); cfg->sync(); KConfigGroup grp = cfg->group( KEY_Plugins() ); QStringList plugins = ShellExtension::getInstance()->defaultPlugins(); if( plugins.isEmpty() ) { for (const KPluginMetaData& info : qAsConst(d->plugins)) { if (!isUserSelectable(info)) { continue; } QJsonValue enabledByDefault = info.rawData()[KEY_KPlugin()].toObject()[KEY_EnabledByDefault()]; // plugins enabled until explicitly specified otherwise if (enabledByDefault.isNull() || enabledByDefault.toBool()) { plugins << info.pluginId(); } } } for (const QString& s : qAsConst(plugins)) { grp.writeEntry(s + KEY_Suffix_Enabled(), true); } grp.sync(); } }