Browse Source

Merge branch 'master' into find_qmake_in_exepath

master
Henner Kollmann 7 years ago
parent
commit
c0183494ec
  1. 1
      .gitignore
  2. 8
      .travis.yml
  3. 76
      CMakeLists.txt
  4. 2
      LICENSE.GPLv3
  5. 2
      LICENSE.LGPLv3
  6. 129
      README.md
  7. 2
      tests/QtWebEngineApplication/main.cpp
  8. 2
      tests/QtWebEngineApplication/main.qml
  9. 24
      tests/tests-ci.sh
  10. 13
      tests/tests-environment.sh
  11. 42
      tools/generate-excludelist.sh
  12. 30
      tools/linuxdeployqt/CMakeLists.txt
  13. 69
      tools/linuxdeployqt/excludelist.h
  14. 34
      tools/linuxdeployqt/linuxdeployqt.pro
  15. 130
      tools/linuxdeployqt/main.cpp
  16. 303
      tools/linuxdeployqt/shared.cpp
  17. 4
      tools/linuxdeployqt/shared.h

1
.gitignore

@ -36,3 +36,4 @@ Makefile*
# QtCtreator CMake
CMakeLists.txt.user
.idea

8
.travis.yml

@ -11,12 +11,18 @@ env:
before_install:
- ./tests/tests-environment.sh
before_script:
# fetch all tags
- git fetch --unshallow
script:
- ./tests/tests-ci.sh
after_success:
- wget -c https://github.com/probonopd/uploadtool/raw/master/upload.sh
- bash ./upload.sh ./linuxdeployqt-*.AppImage
# quick fix for issue 223
- if [ "$TRAVIS_BRANCH" != "master" ]; then export TRAVIS_EVENT_TYPE=pull_request; fi
- bash ./upload.sh ./linuxdeployqt-*.AppImage*
after_script:
- "xpra stop :99"

76
CMakeLists.txt

@ -0,0 +1,76 @@
# CMake configuration for linuxdeployqt
# Not meant to replace the qmake build system, but for use with CMake based IDEs.
cmake_minimum_required(VERSION 3.2)
project(linuxdeployqt)
find_program(GIT git)
# make sure Git revision ID and latest tag is not stored in the CMake cache
# otherwise, one would have to reset the CMake cache on every new commit to make sure the Git commit ID is up to date
unset(GIT_COMMIT CACHE)
unset(GIT_LATEST_TAG CACHE)
if("${GIT}" STREQUAL "GIT-NOTFOUND")
message(FATAL_ERROR "Could not find git")
endif()
# read Git revision ID and latest tag number
execute_process(
COMMAND "${GIT}" rev-parse --short HEAD
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
OUTPUT_VARIABLE GIT_COMMIT
OUTPUT_STRIP_TRAILING_WHITESPACE
RESULT_VARIABLE GIT_COMMIT_RESULT
)
if(NOT GIT_COMMIT_RESULT EQUAL 0)
message(FATAL_ERROR "Failed to determine git commit ID")
endif()
mark_as_advanced(GIT_COMMIT GIT_COMMIT_RESULT)
execute_process(
COMMAND "${GIT}" rev-list --tags --skip=1 --max-count=1
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
OUTPUT_VARIABLE GIT_TAG_ID
OUTPUT_STRIP_TRAILING_WHITESPACE
RESULT_VARIABLE GIT_TAG_ID_RESULT
)
if(NOT GIT_TAG_ID_RESULT EQUAL 0)
message(FATAL_ERROR "Failed to determine git tag ID")
endif()
mark_as_advanced(GIT_TAG_ID GIT_TAG_ID_RESULT)
execute_process(
COMMAND "${GIT}" describe --tags ${GIT_TAG_ID} --abbrev=0
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
OUTPUT_VARIABLE GIT_TAG_NAME
OUTPUT_STRIP_TRAILING_WHITESPACE
RESULT_VARIABLE GIT_TAG_NAME_RESULT
)
if(NOT GIT_TAG_NAME_RESULT EQUAL 0)
message(FATAL_ERROR "Failed to determine git tag name")
endif()
mark_as_advanced(GIT_TAG_NAME GIT_TAG_NAME_RESULT)
# set version and build number
set(VERSION 1-alpha)
if("$ENV{TRAVIS_BUILD_NUMBER}" STREQUAL "")
set(BUILD_NUMBER "<local dev build>")
else()
set(BUILD_NUMBER "$ENV{TRAVIS_BUILD_NUMBER}")
endif()
# get current date
execute_process(
COMMAND env LC_ALL=C date -u "+%Y-%m-%d %H:%M:%S %Z"
OUTPUT_VARIABLE DATE
OUTPUT_STRIP_TRAILING_WHITESPACE
RESULT_VARIABLE DATE_RESULT
)
if(NOT DATE_RESULT EQUAL 0)
message(FATAL_ERROR "Failed to determine date string")
endif()
mark_as_advanced(DATE DATE_RESULT)
add_subdirectory(tools/linuxdeployqt)

2
LICENSE.GPLv3

@ -1,6 +1,6 @@
GNU GENERAL PUBLIC LICENSE
The Qt Toolkit is Copyright (C) 2016 The Qt Company Ltd.
The Qt Toolkit is Copyright (C) 2016-18 The Qt Company Ltd.
Contact: http://www.qt.io/licensing/
You may use, distribute and copy the Qt Toolkit under the terms of

2
LICENSE.LGPLv3

@ -1,6 +1,6 @@
GNU LESSER GENERAL PUBLIC LICENSE
The Qt Toolkit is Copyright (C) 2016 The Qt Company Ltd.
The Qt Toolkit is Copyright (C) 2016-18 The Qt Company Ltd.
Contact: http://www.qt.io/licensing/
You may use, distribute and copy the Qt Toolkit under the terms of

129
README.md

@ -1,6 +1,8 @@
# linuxdeployqt [![Build Status](https://travis-ci.org/probonopd/linuxdeployqt.svg?branch=master)](https://travis-ci.org/probonopd/linuxdeployqt) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/93b4a359057e412b8a7673b4b61d7cb7)](https://www.codacy.com/app/probonopd/linuxdeployqt?utm_source=github.com&amp;utm_medium=referral&amp;utm_content=probonopd/linuxdeployqt&amp;utm_campaign=Badge_Grade) [![discourse](https://img.shields.io/badge/forum-discourse-orange.svg)](http://discourse.appimage.org/t/linuxdeployqt-new-linux-deployment-tool-for-qt/57) [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/probonopd/AppImageKit?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) [![irc](https://img.shields.io/badge/IRC-%23AppImage%20on%20freenode-blue.svg)](https://webchat.freenode.net/?channels=AppImage)
This Linux Deployment Tool for Qt, `linuxdeployqt`, takes an application as input and makes it self-contained by copying in the Qt libraries and plugins that the application uses into a bundle. This can optionally be put into an [AppImage](http://appimage.org/), and, using [fpm](https://github.com/probonopd/linuxdeployqt/issues/9), into cross-distro deb and rpm packages.
This Linux Deployment Tool, `linuxdeployqt`, takes an application as input and makes it self-contained by copying in the resources that the application uses (like libraries, graphics, and plugins) into a bundle. The resulting bundle can be distributed as an AppDir or as an [AppImage](https://appimage.org/) to users, or can be put into cross-distribution packages. It can be used as part of the build process to deploy applications written in C, C++, and other compiled languages with systems like `CMake`, `qmake`, and `make`. When used on Qt-based applications, it can bundle a specific minimal subset of Qt required to run the application.
![](https://user-images.githubusercontent.com/2480569/34471167-d44bd55e-ef41-11e7-941e-e091a83cae38.png)
## Differences to macdeployqt
This tool is conceptually based on the [Mac Deployment Tool](http://doc.qt.io/qt-5/osx-deployment.html), `macdeployqt` in the tools applications of the Qt Toolkit, but has been changed to a slightly different logic and other tools needed for Linux.
@ -22,16 +24,17 @@ Please download __linuxdeployqt-x86_64.AppImage__ from the [Releases](https://gi
Usage: linuxdeployqt app-binary [options]
Options:
-verbose=<0-3> : 0 = no output, 1 = error/warning (default), 2 = normal, 3 = debug
-no-plugins : Skip plugin deployment
-appimage : Create an AppImage
-no-strip : Don't run 'strip' on the binaries
-bundle-non-qt-libs : Also bundle non-core, non-Qt libraries
-executable=<path> : Let the given executable use the deployed libraries too
-qmldir=<path> : Scan for QML imports to bundle from the given directory, determined by Qt's qmlimportscanner
-always-overwrite : Copy files even if the target file exists
-qmake=<path> : The qmake executable to use
-no-translations : Skip deployment of translations
-verbose=<0-3> : 0 = no output, 1 = error/warning (default), 2 = normal, 3 = debug
-no-plugins : Skip plugin deployment
-appimage : Create an AppImage
-no-strip : Don't run 'strip' on the binaries
-bundle-non-qt-libs : Also bundle non-core, non-Qt libraries
-executable=<path> : Let the given executable use the deployed libraries too
-qmldir=<path> : Scan for QML imports to bundle from the given directory, determined by Qt's qmlimportscanner
-always-overwrite : Copy files even if the target file exists
-qmake=<path> : The qmake executable to use
-no-translations : Skip deployment of translations
-extra-plugins=<list> : List of extra plugins which should be deployed, separated by comma
linuxdeployqt takes an application as input and makes it
self-contained by copying in the Qt libraries and plugins that
@ -40,12 +43,43 @@ the application uses.
#### Simplest example
Given that a desktop file should be provided with an AppImage, `linuxdeployqt` can use that to determine the parameters of the build.
You'll need to provide the basic structure of an `AppDir` which should look something like this:
```
└── usr
├── bin
│   └── your_app
├── lib
└── share
├── applications
│   └── your_app.desktop
└── icons
└── <theme>
└── <resolution>
└── apps
└── your_app.png
```
Replace `<theme>` and `<resolution>` with (for example) `hicolor` and `256x256` respectively; see [icon theme spec](https://specifications.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html) for more details.
Using the desktop file `linuxdeployqt` can determine the parameters of the build.
`linuxdeployqt ./path/to/appdir/usr/share/application_name.desktop`
Where your desktop file would look something like:
```
[Desktop Entry]
Type=Application
Name=Amazing Qt App
Comment=The best Qt Application Ever
Exec=your_app
Icon=your_app
Categories=Office;
```
* Notice that both `Exec` and `Icon` only have file names.
* Also Notice that the `Icon` entry does not include an extension.
Where the _desktop_ file specifies the executable to be run (with `EXEC=`), the name of the applications and an icon.
See [desktop file specification](https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html).
Read more about desktop files in the [freedesktop specification here](https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html).
Now you can say: `linuxdeployqt-continuous-x86_64.AppImage path/to/AppDir/usr/share/applications/your_app.desktop`
For a more detailed example, see "Using linuxdeployqt with Travis CI" below.
@ -78,6 +112,18 @@ find $HOME/build-*-*_Qt_* \( -name "moc_*" -or -name "*.o" -or -name "qrc_*" -or
Alternatively, you could use `$DESTDIR`.
#### Adding extra Qt plugins
If you want aditional plugins which the tool doesn't deploy, for a variety of reasons, you can use the -extra-plugins argument and include a list of plugins separated by a comma.
The plugins deployed are from the Qt installation pointed out by `qmake -v`.
You can deploy entire plugin directories, a specific directory or a mix of both.
Usage examples:
1. `-extra-plugins=sqldrivers/libqmsql.so,iconengines/libqsvgicon.so`
2. `-extra-plugins=sqldrivers,iconengines/libqsvgicon.so`
3. `-extra-plugins=sqldrivers,iconengines,mediaservice,gamepads`
## Using linuxdeployqt with Travis CI
A common use case for `linuxdeployqt` is to use it on Travis CI after the `make` command. The following example illustrates how to use `linuxdeployqt` with Travis CI. Create a `.travis.yml` file similar to this one (be sure to customize it, e.g., change `APPNAME` to the name of your application as it is spelled in the `Name=` entry of the `.desktop` file):
@ -89,26 +135,34 @@ sudo: require
dist: trusty
before_install:
- sudo add-apt-repository ppa:beineri/opt-qt59-trusty -y
- sudo apt-get update -qq
- sudo add-apt-repository ppa:beineri/opt-qt593-trusty -y
- sudo apt-get update -qq
install:
- sudo apt-get -y install qt59base
- source /opt/qt*/bin/qt*-env.sh
- sudo apt-get -y install qt59base
- source /opt/qt*/bin/qt*-env.sh
script:
- qmake CONFIG+=release PREFIX=/usr
- make -j$(nproc)
- make INSTALL_ROOT=appdir -j$(nproc) install ; find appdir/
- wget -c "https://github.com/probonopd/linuxdeployqt/releases/download/continuous/linuxdeployqt-continuous-x86_64.AppImage"
- chmod a+x linuxdeployqt*.AppImage
- wget -c -nv "https://github.com/probonopd/linuxdeployqt/releases/download/continuous/linuxdeployqt-continuous-x86_64.AppImage"
- chmod a+x linuxdeployqt-continuous-x86_64.AppImage
- unset QTDIR; unset QT_PLUGIN_PATH ; unset LD_LIBRARY_PATH
- ./linuxdeployqt*.AppImage ./appdir/usr/share/applications/*.desktop -bundle-non-qt-libs
- ./linuxdeployqt*.AppImage ./appdir/usr/share/applications/*.desktop -appimage
- export VERSION=$(git rev-parse --short HEAD) # linuxdeployqt uses this for naming the file
- ./linuxdeployqt-continuous-x86_64.AppImage appdir/usr/share/applications/*.desktop -bundle-non-qt-libs
- ./linuxdeployqt-continuous-x86_64.AppImage appdir/usr/share/applications/*.desktop -appimage
after_success:
- find ./appdir -executable -type f -exec ldd {} \; | grep " => /usr" | cut -d " " -f 2-3 | sort | uniq
- curl --upload-file ./APPNAME*.AppImage https://transfer.sh/APPNAME-git.$(git rev-parse --short HEAD)-x86_64.AppImage
- find appdir -executable -type f -exec ldd {} \; | grep " => /usr" | cut -d " " -f 2-3 | sort | uniq
- # curl --upload-file APPNAME*.AppImage https://transfer.sh/APPNAME-git.$(git rev-parse --short HEAD)-x86_64.AppImage
- wget -c https://github.com/probonopd/uploadtool/raw/master/upload.sh
- bash upload.sh APPNAME*.AppImage*
branches:
except:
- # Do not build tags that we create when we upload to GitHub Releases
- /^(?i:continuous)/
```
When you save your change, then Travis CI should build and upload an AppImage for you. More likely than not, some fine-tuning will still be required.
@ -124,6 +178,10 @@ find: `appdir/': No such file or directory
If `qmake` does not allow for `make install` or does not install the desktop file and icon, then you need to change your `.pro` file it similar to https://github.com/probonopd/FeedTheMonkey/blob/master/FeedTheMonkey.pro.
It is common on Unix to also use the build tool to install applications and libraries; for example, by invoking `make install`. For this reason, `qmake` has the concept of an install set, an object which contains instructions about the way a part of a project is to be installed.
Please see the section "Installing Files" on http://doc.qt.io/qt-5/qmake-advanced-usage.html.
```
- make INSTALL_ROOT=appdir install ; find appdir/
```
@ -139,9 +197,9 @@ __CMake__ wants `DESTDIR` instead:
__autotools__ (the dinosaur that spends precious minutes "checking...") wants `DESTDIR` too but insists on an absolute link which we can feed it using readlink:
```
- ./configure --prefix=/usr
- make -j$(nproc)
- make install DESTDIR=$(readlink -f appdir) ; find appdir/
- ./configure --prefix=/usr
- make -j$(nproc)
- make install DESTDIR=$(readlink -f appdir) ; find appdir/
```
Caution if you encounter
@ -159,9 +217,7 @@ The exception is that you are building Qt libraries that _should_ be installed t
`linuxdeployqt` is great for upstream application projects that want to release their software in binary form to Linux users quickly and without much overhead. If you would like to see a particular application use `linuxdeployqt`, then sending a Pull Request may be an option to get the upstream application project to consider it. You can use the following template text for Pull Requests but make sure to customize it to the project in question.
```
This PR, when merged, will compile this application on [Travis CI](https://travis-ci.org/) upon each `git push`, and upload an [AppImage](http://appimage.org/) to a temporary download URL on transfer.sh (available for 14 days). The download URL is toward the end of each Travis CI build log of each build (see below for how to set up automatic uploading to your GitHub Releases page).
For this to work, you need to enable Travis CI for your repository as [described here](https://travis-ci.org/getting_started) __prior to merging this__, if you haven't already done so.
This PR, when merged, will compile this application on [Travis CI](https://travis-ci.org/) upon each `git push`, and upload an [AppImage](http://appimage.org/) to your GitHub Releases page.
Providing an [AppImage](http://appimage.org/) would have, among others, these advantages:
- Applications packaged as an AppImage can run on many distributions (including Ubuntu, Fedora, openSUSE, CentOS, elementaryOS, Linux Mint, and others)
@ -175,12 +231,12 @@ Providing an [AppImage](http://appimage.org/) would have, among others, these ad
- Can optionally GPG2-sign your AppImages (inside the file)
- Works on Live ISOs
- Can use the same AppImages when dual-booting multiple distributions
- Can be listed in the [AppImageHub](https://appimage.github.io/) central directory of available AppImages
[Here is an overview](https://github.com/probonopd/AppImageKit/wiki/AppImages) of projects that are already distributing upstream-provided, official AppImages.
- Can be listed in the [AppImageHub](https://appimage.github.io/apps) central directory of available AppImages
- Can double as a self-extracting compressed archive with the `--appimage-extract` parameter
__Please note:__ Instead of storing AppImage builds temporarily for 14 days each on transfer.sh, you could use GitHub Releases to store the binaries permanently. This way, they would be visible on the Releases page of your project. This is what I recommend. See https://docs.travis-ci.com/user/deployment/releases/. If you want to do this for continuous builds, also see https://github.com/probonopd/uploadtool.
[Here is an overview](https://appimage.github.io/apps) of projects that are already distributing upstream-provided, official AppImages.
__PLEASE NOTE:__ For this to work, you need to enable Travis CI for your repository as [described here](https://travis-ci.org/getting_started) __prior to merging this__, if you haven't already done so. Also, You need to set up `GITHUB_TOKEN` in Travis CI for this to work; please see https://github.com/probonopd/uploadtool.
If you would like to see only one entry for the Pull Request in your project's history, then please enable [this GitHub functionality](https://help.github.com/articles/configuring-commit-squashing-for-pull-requests/) on your repo. It allows you to squash (combine) the commits when merging.
If you have questions, AppImage developers are on #AppImage on irc.freenode.net.
@ -189,6 +245,8 @@ If you have questions, AppImage developers are on #AppImage on irc.freenode.net.
## Projects using linuxdeployqt
These projects are already using [Travis CI](http://travis-ci.org/) and linuxdeployqt to provide AppImages of their builds:
- https://github.com/probonopd/ImageMagick
- https://github.com/Subsurface-divelog/subsurface/
- https://github.com/jimevins/glabels-qt
- https://travis-ci.org/NeoTheFox/RepRaptor
- https://github.com/electronpass/electronpass-desktop
@ -211,6 +269,7 @@ These projects are already using [Travis CI](http://travis-ci.org/) and linuxdep
- https://github.com/probonopd/linuxdeployqt/ obviously ;-)
- https://github.com/xdgurl/xdgurl
- https://github.com/QNapi/qnapi
- https://github.com/m-o-s-t-a-f-a/dana
This project is already using linuxdeployqt in a custom Jenkins workflow:
- https://github.com/appimage-packages/

2
tests/QtWebEngineApplication/main.cpp

@ -1,6 +1,6 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Copyright (C) 2016-18 The Qt Company Ltd.
** Contact: http://www.qt.io/licensing/
**
** This file is part of the examples of the Qt Toolkit.

2
tests/QtWebEngineApplication/main.qml

@ -1,6 +1,6 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Copyright (C) 2016-18 The Qt Company Ltd.
** Contact: http://www.qt.io/licensing/
**
** This file is part of the examples of the Qt Toolkit.

24
tests/tests-ci.sh

@ -6,11 +6,16 @@ source /opt/qt*/bin/qt*-env.sh
/opt/qt*/bin/qmake CONFIG+=release CONFIG+=force_debug_info linuxdeployqt.pro
make -j
mkdir -p linuxdeployqt.AppDir/usr/bin/
cp /usr/bin/patchelf /usr/local/bin/{appimagetool,mksquashfs,zsyncmake} linuxdeployqt.AppDir/usr/bin/
# exit on failure
set -e
mkdir -p linuxdeployqt.AppDir/usr/{bin,lib}
cp /usr/bin/{patchelf,desktop-file-validate} /usr/local/bin/{appimagetool,zsyncmake} linuxdeployqt.AppDir/usr/bin/
cp ./bin/linuxdeployqt linuxdeployqt.AppDir/usr/bin/
cp -r /usr/local/lib/appimagekit linuxdeployqt.AppDir/usr/lib/
find linuxdeployqt.AppDir/
export VERSION=continuous
cp ./bin/linuxdeployqt linuxdeployqt.AppDir/usr/bin/
./bin/linuxdeployqt linuxdeployqt.AppDir/usr/bin/desktop-file-validate -verbose=3 -bundle-non-qt-libs
./bin/linuxdeployqt linuxdeployqt.AppDir/linuxdeployqt.desktop -verbose=3 -appimage
ls -lh
find *.AppDir
@ -31,9 +36,18 @@ ulimit -c unlimited
ulimit -a -S
ulimit -a -H
bash -e tests/tests.sh
# error handling performed separately
set +e
# print version number
./linuxdeployqt-*-x86_64.AppImage --version
# TODO: reactivate tests
#bash -e tests/tests.sh
true
RESULT=$?
if [ $? -ne 0 ]; then
if [ $RESULT -ne 0 ]; then
echo "FAILURE: linuxdeployqt CRASHED -- uploading files for debugging to transfer.sh"
set -v
[ -e /tmp/coredump ] && curl --upload-file /tmp/coredump https://transfer.sh/coredump

13
tests/tests-environment.sh

@ -2,17 +2,20 @@
set -e
sudo add-apt-repository --yes ppa:beineri/opt-qt59-trusty
sudo add-apt-repository --yes ppa:beineri/opt-qt593-trusty
sudo apt-get update -qq
wget http://ftp.de.debian.org/debian/pool/main/p/patchelf/patchelf_0.8-2_amd64.deb
wget https://ftp.fau.de/debian/pool/main/p/patchelf/patchelf_0.8-2_amd64.deb
echo "5d506507df7c02766ae6c3ca0d15b4234f4cb79a80799190ded9d3ca0ac28c0c patchelf_0.8-2_amd64.deb" | sha256sum -c
sudo dpkg -i patchelf_0.8-2_amd64.deb
cd /tmp/
wget -c "https://github.com/probonopd/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage"
wget -c "https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage"
chmod +x appimagetool*AppImage
./appimagetool*AppImage --appimage-extract
sudo cp squashfs-root/usr/bin/* /usr/local/bin
sudo cp squashfs-root/usr/bin/* /usr/local/bin/
sudo cp -r squashfs-root/usr/lib/appimagekit /usr/local/lib/
sudo chmod +rx /usr/local/lib/appimagekit
cd -
sudo apt-get -y install qt59base qt59declarative qt59webengine binutils xpra
sudo apt-get -y install qt59base qt59declarative qt59webengine binutils xpra zsync desktop-file-utils gcc g++ make libgl1-mesa-dev fuse psmisc qt59translations

42
tools/generate-excludelist.sh

@ -0,0 +1,42 @@
#!/bin/bash
set -e
# download excludelist
blacklisted=($(wget --quiet https://raw.githubusercontent.com/probonopd/AppImages/master/excludelist -O - | sort | uniq | grep -v "^#.*" | grep "[^-\s]"))
# sanity check
if [ "$blacklisted" == "" ]; then
exit 1;
fi
filename=$(readlink -f $(dirname "$0"))/linuxdeployqt/excludelist.h
# overwrite existing source file
cat > "$filename" <<EOF
/*
* List of libraries to exclude for different reasons.
*
* Automatically generated from
* https://raw.githubusercontent.com/probonopd/AppImages/master/excludelist
*
* This file shall be committed by the developers occassionally,
* otherwise systems without access to the internet won't be able to build
* fully working versions of linuxdeployqt.
*
* See https://github.com/probonopd/linuxdeployqt/issues/274 for more
* information.
*/
#include <QStringList>
static const QStringList generatedExcludelist = {
EOF
# Create array
for item in ${blacklisted[@]:0:${#blacklisted[@]}-1}; do
echo -e ' "'"$item"'",' >> "$filename"
done
echo -e ' "'"${blacklisted[$((${#blacklisted[@]}-1))]}"'"' >> "$filename"
echo "};" >> "$filename"

30
tools/linuxdeployqt/CMakeLists.txt

@ -0,0 +1,30 @@
set(CMAKE_AUTOMOC ON)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# expose version data as compiler definition
add_definitions("-DLINUXDEPLOYQT_VERSION=\"${GIT_TAG_NAME}\"")
add_definitions("-DLINUXDEPLOYQT_GIT_COMMIT=\"${GIT_COMMIT}\"")
add_definitions("-DBUILD_DATE=\"${DATE}\"")
add_definitions("-DBUILD_NUMBER=\"${BUILD_NUMBER}\"")
find_package(Qt5 REQUIRED COMPONENTS Core)
# update excludelist
message(STATUS "Updating excludelist...")
execute_process(
COMMAND bash ${CMAKE_CURRENT_SOURCE_DIR}/../generate-excludelist.sh
OUTPUT_VARIABLE EXCLUDELIST
TIMEOUT 10
RESULT_VARIABLE EXCLUDELIST_RESULT
)
if(NOT EXCLUDELIST_RESULT EQUAL 0)
message(WARNING "Updating excludelist failed, using outdated copy")
endif()
mark_as_advanced(EXCLUDELIST EXCLUDELIST_RESULT)
add_executable(linuxdeployqt main.cpp shared.cpp)
target_include_directories(linuxdeployqt PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(linuxdeployqt Qt5::Core)
target_compile_definitions(linuxdeployqt PRIVATE -DEXCLUDELIST="${EXCLUDELIST}")

69
tools/linuxdeployqt/excludelist.h

@ -0,0 +1,69 @@
/*
* List of libraries to exclude for different reasons.
*
* Automatically generated from
* https://raw.githubusercontent.com/probonopd/AppImages/master/excludelist
*
* This file shall be committed by the developers occassionally,
* otherwise systems without access to the internet won't be able to build
* fully working versions of linuxdeployqt.
*
* See https://github.com/probonopd/linuxdeployqt/issues/274 for more
* information.
*/
#include <QStringList>
static const QStringList generatedExcludelist = {
"ld-linux.so.2",
"ld-linux-x86-64.so.2",
"libanl.so.1",
"libasound.so.2",
"libBrokenLocale.so.1",
"libcidn.so.1",
"libcom_err.so.2",
"libcrypt.so.1",
"libc.so.6",
"libdl.so.2",
"libdrm.so.2",
"libexpat.so.1",
"libfontconfig.so.1",
"libfreetype.so.6",
"libgcc_s.so.1",
"libgdk_pixbuf-2.0.so.0",
"libgio-2.0.so.0",
"libglib-2.0.so.0",
"libGL.so.1",
"libgobject-2.0.so.0",
"libgpg-error.so.0",
"libharfbuzz.so.0",
"libICE.so.6",
"libjack.so.0",
"libkeyutils.so.1",
"libm.so.6",
"libmvec.so.1",
"libnsl.so.1",
"libnss_compat.so.2",
"libnss_db.so.2",
"libnss_dns.so.2",
"libnss_files.so.2",
"libnss_hesiod.so.2",
"libnss_nisplus.so.2",
"libnss_nis.so.2",
"libp11-kit.so.0",
"libpango-1.0.so.0",
"libpangocairo-1.0.so.0",
"libpangoft2-1.0.so.0",
"libpthread.so.0",
"libresolv.so.2",
"librt.so.1",
"libSM.so.6",
"libstdc++.so.6",
"libthread_db.so.1",
"libusb-1.0.so.0",
"libutil.so.1",
"libuuid.so.1",
"libX11.so.6",
"libxcb.so.1",
"libz.so.1"
};

34
tools/linuxdeployqt/linuxdeployqt.pro

@ -12,6 +12,38 @@ load(qt_tool)
HEADERS += shared.h
SOURCES += main.cpp \
shared.cpp
shared.cpp
DEFINES -= QT_USE_QSTRINGBUILDER #leads to compile errors if not disabled
# versioning
# don't break the quotes -- at the moment, the shell commands are injected into the Makefile to have them run on every
# build and not during configure time
DEFINES += LINUXDEPLOYQT_GIT_COMMIT="'\"$(shell cd $$PWD && git rev-parse --short HEAD)\"'"
DEFINES += BUILD_DATE="'\"$(shell env LC_ALL=C date -u '+%Y-%m-%d %H:%M:%S %Z')\"'"
_BUILD_NUMBER = $$(TRAVIS_BUILD_NUMBER)
isEmpty(_BUILD_NUMBER) {
message("Not building on Travis CI, tagging build as local dev build")
DEFINES += BUILD_NUMBER="'\"<local dev build>\"'"
} else {
message("Building on Travis CI build, build number $$_BUILD_NUMBER")
DEFINES += BUILD_NUMBER="'\"$$_BUILD_NUMBER\"'"
}
DEFINES += LINUXDEPLOYQT_VERSION="'\"$(shell cd $$PWD && git describe --tags $(shell cd $$PWD && git rev-list --tags --skip=1 --max-count=1) --abbrev=0)\"'"
contains(DEFINES, EXCLUDELIST.*) {
message("EXCLUDELIST specified, to use the most recent exclude list, please run qmake without EXCLUDELIST definition and with internet.")
} else {
message("Updating exclude list...")
# check whether command _would_ run successfully
EXCLUDELIST_GENERATION_WORKS = FALSE
system($$_PRO_FILE_PWD_/../generate-excludelist.sh): EXCLUDELIST_GENERATION_WORKS = TRUE
isEqual(EXCLUDELIST_GENERATION_WORKS, FALSE) {
warning("Updating excludelist failed, using outdated copy")
}
}

130
tools/linuxdeployqt/main.cpp

@ -1,6 +1,6 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd. and Simon Peter
** Copyright (C) 2016-18 The Qt Company Ltd. and Simon Peter
** Contact: https://www.qt.io/licensing/
**
** This file is part of the tools applications of the Qt Toolkit.
@ -33,6 +33,8 @@
#include <stdlib.h>
#include <QSettings>
#include <QDirIterator>
#include <sstream>
#include "excludelist.h"
int main(int argc, char **argv)
{
@ -43,37 +45,69 @@ int main(int argc, char **argv)
QString firstArgument = QString::fromLocal8Bit(argv[1]);
if (argc < 2 || firstArgument.startsWith("-")) {
qDebug() << "Usage: linuxdeployqt <app-binary|desktop file> [options]";
qDebug() << "";
qDebug() << "Options:";
qDebug() << " -verbose=<0-3> : 0 = no output, 1 = error/warning (default), 2 = normal, 3 = debug";
qDebug() << " -no-plugins : Skip plugin deployment";
qDebug() << " -appimage : Create an AppImage (implies -bundle-non-qt-libs)";
qDebug() << " -no-strip : Don't run 'strip' on the binaries";
qDebug() << " -bundle-non-qt-libs : Also bundle non-core, non-Qt libraries";
qDebug() << " -executable=<path> : Let the given executable use the deployed libraries too";
qDebug() << " -qmldir=<path> : Scan for QML imports in the given path";
qDebug() << " -always-overwrite : Copy files even if the target file exists";
qDebug() << " -qmake=<path> : The qmake executable to use";
qDebug() << " -no-translations : Skip deployment of translations.";
qDebug() << "";
qDebug() << "linuxdeployqt takes an application as input and makes it";
qDebug() << "self-contained by copying in the Qt libraries and plugins that";
qDebug() << "the application uses.";
qDebug() << "";
qDebug() << "By default it deploys the Qt instance that qmake on the $PATH points to.";
qDebug() << "The '-qmake' option can be used to point to the qmake executable";
qDebug() << "to be used instead.";
qDebug() << "";
qDebug() << "Plugins related to a Qt library are copied in with the library.";
// print version statement
std::stringstream version;
version << "linuxdeployqt " << LINUXDEPLOYQT_VERSION
<< " (commit " << LINUXDEPLOYQT_GIT_COMMIT << "), "
<< "build " << BUILD_NUMBER << " built on " << BUILD_DATE;
qInfo().noquote() << QString::fromStdString(version.str());
// due to the structure of the argument parser, we have to check all arguments at first to check whether the user
// wants to get the version only
// TODO: replace argument parser with position independent, less error prone version
for (int i = 0; i < argc; i++ ) {
QString argument = argv[i];
if (argument == "-version" || argument == "-V" || argument == "--version") {
// can just exit normally, version has been printed above
return 0;
}
if (argument == QByteArray("-show-exclude-libs")) {
qInfo() << generatedExcludelist;
return 0;
}
}
if (argc < 2 || (firstArgument.startsWith("-"))) {
qInfo() << "";
qInfo() << "Usage: linuxdeployqt <app-binary|desktop file> [options]";
qInfo() << "";
qInfo() << "Options:";
qInfo() << " -always-overwrite : Copy files even if the target file exists.";
qInfo() << " -appimage : Create an AppImage (implies -bundle-non-qt-libs).";
qInfo() << " -bundle-non-qt-libs : Also bundle non-core, non-Qt libraries.";
qInfo() << " -exclude-libs=<list> : List of libraries which should be excluded,";
qInfo() << " separated by comma.";
qInfo() << " -executable=<path> : Let the given executable use the deployed libraries";
qInfo() << " too";
qInfo() << " -extra-plugins=<list> : List of extra plugins which should be deployed,";
qInfo() << " separated by comma.";
qInfo() << " -no-copy-copyright-files : Skip deployment of copyright files.";
qInfo() << " -no-plugins : Skip plugin deployment.";
qInfo() << " -no-strip : Don't run 'strip' on the binaries.";
qInfo() << " -no-translations : Skip deployment of translations.";
qInfo() << " -qmake=<path> : The qmake executable to use.";
qInfo() << " -qmldir=<path> : Scan for QML imports in the given path.";
qInfo() << " -show-exclude-libs : Print exclude libraries list.";
qInfo() << " -verbose=<0-3> : 0 = no output, 1 = error/warning (default),";
qInfo() << " 2 = normal, 3 = debug.";
qInfo() << " -version : Print version statement and exit.";
qInfo() << "";
qInfo() << "linuxdeployqt takes an application as input and makes it";
qInfo() << "self-contained by copying in the Qt libraries and plugins that";
qInfo() << "the application uses.";
qInfo() << "";
qInfo() << "By default it deploys the Qt instance that qmake on the $PATH points to.";
qInfo() << "The '-qmake' option can be used to point to the qmake executable";
qInfo() << "to be used instead.";
qInfo() << "";
qInfo() << "Plugins related to a Qt library are copied in with the library.";
/* TODO: To be implemented
qDebug() << "The accessibility, image formats, and text codec";
qDebug() << "plugins are always copied, unless \"-no-plugins\" is specified.";
*/
qDebug() << "";
qDebug() << "See the \"Deploying Applications on Linux\" topic in the";
qDebug() << "documentation for more information about deployment on Linux.";
qInfo() << "";
qInfo() << "See the \"Deploying Applications on Linux\" topic in the";
qInfo() << "documentation for more information about deployment on Linux.";
return 1;
}
@ -88,6 +122,12 @@ int main(int argc, char **argv)
* to do when using linuxdeployqt. */
if (firstArgument.endsWith(".desktop")){
qDebug() << "Desktop file as first argument:" << firstArgument;
/* Check if the desktop file really exists */
if (! QFile::exists(firstArgument)) {
LogError() << "Desktop file in first argument does not exist!";
return 1;
}
QSettings * settings = 0;
settings = new QSettings(firstArgument, QSettings::IniFormat);
desktopExecEntry = settings->value("Desktop Entry/Exec", "r").toString().split(' ').first().split('/').last().trimmed();
@ -167,13 +207,16 @@ int main(int argc, char **argv)
extern bool bundleAllButCoreLibs;
extern bool fhsLikeMode;
extern QString fhsPrefix;
extern bool alwaysOwerwriteEnabled;
extern QStringList librarySearchPath;
extern bool alwaysOwerwriteEnabled;
QStringList additionalExecutables;
bool qmldirArgumentUsed = false;
bool skipTranslations = false;
QStringList qmlDirs;
QString qmakeExecutable;
extern QStringList extraQtPlugins;
extern QStringList excludeLibs;
extern bool copyCopyrightFiles;
/* FHS-like mode is for an application that has been installed to a $PREFIX which is otherwise empty, e.g., /path/to/usr.
* In this case, we want to construct an AppDir in /path/to. */
@ -299,7 +342,7 @@ int main(int argc, char **argv)
}
if(QFileInfo(appDirPath + "/" + desktopIconEntry + ".svgz").exists() == true){
preExistingToplevelIcon = appDirPath + "/" + desktopIconEntry + ".svgz";
if(QFileInfo(appDirPath + "/.DirIcon").exists() == false) if(QFileInfo(appDirPath + "/.DirIcon").exists() == false) QFile::copy(preExistingToplevelIcon, appDirPath + "/.DirIcon");
if(QFileInfo(appDirPath + "/.DirIcon").exists() == false) QFile::copy(preExistingToplevelIcon, appDirPath + "/.DirIcon");
}
if(QFileInfo(appDirPath + "/" + desktopIconEntry + ".svg").exists() == true){
preExistingToplevelIcon = appDirPath + "/" + desktopIconEntry + ".svg";
@ -326,8 +369,10 @@ int main(int argc, char **argv)
}
}
// Check arguments
for (int i = 2; i < argc; ++i) {
QByteArray argument = QByteArray(argv[i]);
if (argument == QByteArray("-no-plugins")) {
LogDebug() << "Argument found:" << argument;
plugins = false;
@ -365,6 +410,9 @@ int main(int argc, char **argv)
LogError() << "Missing qml directory path";
else
qmlDirs << argument.mid(index+1);
} else if (argument.startsWith("-no-copy-copyright-files")) {
LogDebug() << "Argument found:" << argument;
copyCopyrightFiles = false;
} else if (argument == QByteArray("-always-overwrite")) {
LogDebug() << "Argument found:" << argument;
alwaysOwerwriteEnabled = true;
@ -375,11 +423,22 @@ int main(int argc, char **argv)
} else if (argument == QByteArray("-no-translations")) {
LogDebug() << "Argument found:" << argument;
skipTranslations = true;
} else if (argument.startsWith("-")) {
LogError() << "Unknown argument" << argument << "\n";
} else if (argument.startsWith("-extra-plugins=")) {
LogDebug() << "Argument found:" << argument;
int index = argument.indexOf("=");
extraQtPlugins = QString(argument.mid(index + 1)).split(",");
} else if (argument.startsWith("-exclude-libs=")) {
LogDebug() << "Argument found:" << argument;
int index = argument.indexOf("=");
excludeLibs = QString(argument.mid(index + 1)).split(",");
} else if (argument.startsWith("--")) {
LogError() << "Error: arguments must not start with --, only -:" << argument << "\n";
return 1;
} else {
LogError() << "Unknown argument:" << argument << "\n";
return 1;
}
}
}
if (appimage) {
if(checkAppImagePrerequisites(appDirPath) == false){
@ -388,6 +447,11 @@ int main(int argc, char **argv)
}
}
if (!excludeLibs.isEmpty())
{
qWarning() << "WARNING: Excluding the following libraries might break the AppImage. Please double-check the list:" << excludeLibs;
}
DeploymentInfo deploymentInfo = deployQtLibraries(appDirPath, additionalExecutables,
qmakeExecutable);

303
tools/linuxdeployqt/shared.cpp

@ -1,6 +1,6 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd. and Simon Peter
** Copyright (C) 2016-18 The Qt Company Ltd. and Simon Peter
** Contact: https://www.qt.io/licensing/
**
** This file is part of the tools applications of the Qt Toolkit.
@ -44,6 +44,7 @@
#include <QRegularExpression>
#include <QStandardPaths>
#include "shared.h"
#include "excludelist.h"
QString appBinaryPath;
bool runStripEnabled = true;
@ -57,6 +58,9 @@ int logLevel = 1;
int qtDetected = 0;
bool qtDetectionComplete = 0; // As long as Qt is not detected yet, ldd may encounter "not found" messages, continue anyway
bool deployLibrary = false;
QStringList extraQtPlugins;
QStringList excludeLibs;
bool copyCopyrightFiles = true;
using std::cout;
using std::endl;
@ -212,38 +216,22 @@ inline QDebug operator<<(QDebug debug, const AppDirInfo &info)
return debug;
}
// Determine whether the given 'ldd' output contains a Linux VDSO
// shared object. The name of the VDSO object differs depending
// on architecture. See "vDSO names" in the notes section of vdso(7)
// for more information.
static bool lddOutputContainsLinuxVDSO(const QString &lddOutput) {
// aarch64, arm, mips, x86_64, x86/x32
if (lddOutput.contains(QStringLiteral("linux-vdso.so.1"))) {
return true;
// ppc32, s390
} else if (lddOutput.contains(QStringLiteral("linux-vdso32.so.1"))) {
return true;
// ppc64, s390x
} else if (lddOutput.contains(QStringLiteral("linux-vdso64.so.1"))) {
return true;
// ia64, sh, i386
} else if (lddOutput.contains(QStringLiteral("linux-gate.so.1"))) {
return true;
}
return false;
}
bool copyFilePrintStatus(const QString &from, const QString &to)
{
if (QFile(to).exists()) {
if (alwaysOwerwriteEnabled) {
QFile(to).remove();
} else {
LogDebug() << QFileInfo(to).fileName() << "already deployed, skipping.";
return false;
LogDebug() << QFileInfo(to).fileName() << "already exists at target location";
return true;
}
}
QDir dir(QDir::cleanPath(to + "/../"));
if (!dir.exists()) {
dir.mkpath(".");
}
if (QFile::copy(from, to)) {
QFile dest(to);
dest.setPermissions(dest.permissions() | QFile::WriteOwner | QFile::WriteUser);
@ -270,6 +258,81 @@ bool copyFilePrintStatus(const QString &from, const QString &to)
}
}
bool copyCopyrightFile(QString libPath){
/* When deploying files (e.g., libraries) from the
* system, then try to also deploy their copyright file.
* This is currently only implemented for dpkg-based,
* Debian-like systems. Pull requests welcome for other
* systems. */
if (!copyCopyrightFiles) {
LogNormal() << "Skipping copyright files deployment as requested by the user";
return false;
}
QString dpkgPath;
dpkgPath = QStandardPaths::findExecutable("dpkg");
if(dpkgPath == ""){
LogNormal() << "dpkg not found, hence not deploying copyright files";
return false;
}
QString dpkgQueryPath;
dpkgQueryPath = QStandardPaths::findExecutable("dpkg-query");
if(dpkgQueryPath == ""){
LogNormal() << "dpkg-query not found, hence not deploying copyright files";
return false;
}
QString copyrightFilePath;
/* Find out which package the file being deployed belongs to */
QStringList arguments;
arguments << "-S" << libPath;
QProcess *myProcess = new QProcess();
myProcess->start(dpkgPath, arguments);
myProcess->waitForFinished();
QString strOut = myProcess->readAllStandardOutput().split(':')[0];
if(strOut == "") return false;
/* Find out the copyright file in that package */
arguments << "-L" << strOut;
myProcess->start(dpkgQueryPath, arguments);
myProcess->waitForFinished();
strOut = myProcess->readAllStandardOutput();
QStringList outputLines = strOut.split("\n", QString::SkipEmptyParts);
foreach (QString outputLine, outputLines) {
if((outputLine.contains("usr/share/doc")) && (outputLine.contains("/copyright")) && (outputLine.contains(" "))){
// copyrightFilePath = outputLine.split(' ')[1]; // This is not working on multiarch systems; see https://github.com/probonopd/linuxdeployqt/issues/184#issuecomment-345293540
QStringList parts = outputLine.split(' ');
copyrightFilePath = parts[parts.size() - 1]; // Grab last element
break;
}
}
if(copyrightFilePath == "") return false;
LogDebug() << "copyrightFilePath:" << copyrightFilePath;
/* Where should we copy this file to? We are assuming the Debian-like path contains
* the name of the package like so: copyrightFilePath: "/usr/share/doc/libpcre3/copyright"
* this assumption is most likely only true for Debian-like systems */
QString packageName = copyrightFilePath.split("/")[copyrightFilePath.split("/").length()-2];
QString copyrightFileTargetPath;
if(fhsLikeMode){
copyrightFileTargetPath = QDir::cleanPath(appBinaryPath + "/../../share/doc/" + packageName + "/copyright");
} else {
copyrightFileTargetPath = QDir::cleanPath(appBinaryPath + "/../doc/" + packageName + "/copyright");
}
/* Do the actual copying */
return(copyFilePrintStatus(copyrightFilePath, copyrightFileTargetPath));
}
LddInfo findDependencyInfo(const QString &binaryPath)
{
LddInfo info;
@ -309,20 +372,19 @@ LddInfo findDependencyInfo(const QString &binaryPath)
// LogDebug() << "ldd outputLine:" << outputLine;
if ((outputLine.contains("not found")) && (qtDetectionComplete == 1)){
LogError() << "ldd outputLine:" << outputLine.replace("\t", "");
LogError() << "for binary:" << binaryPath;
LogError() << "Please ensure that all libraries can be found by ldd. Aborting.";
exit(1);
}
}
if ((binaryPath.contains(".so.") || binaryPath.endsWith(".so")) && (!lddOutputContainsLinuxVDSO(output))) {
const QRegularExpressionMatch match = regexp.match(outputLines.first());
if (match.hasMatch()) {
info.installName = match.captured(1);
} else {
LogError() << "Could not parse ldd output line:" << outputLines.first();
}
outputLines.removeFirst();
/*
FIXME: For unknown reasons, this segfaults; see https://travis-ci.org/probonopd/Labrador/builds/339803886#L1320
if (binaryPath.contains("platformthemes")) {
LogDebug() << "Not adding dependencies of" << binaryPath << "because we do not bundle dependencies of platformthemes";
return info;
}
*/
foreach (const QString &outputLine, outputLines) {
const QRegularExpressionMatch match = regexp.match(outputLine);
@ -373,16 +435,15 @@ LibraryInfo parseLddLibraryLine(const QString &line, const QString &appDirPath,
This is more suitable for bundling in a way that is portable between different distributions and target systems.
Along the way, this also takes care of non-Qt libraries.
The excludelist can be updated by running
#/bin/bash
blacklisted=$(wget https://raw.githubusercontent.com/probonopd/AppImages/master/excludelist -O - | sort | uniq | grep -v "^#.*" | grep "[^-\s]")
for item in $blacklisted; do
echo -ne '"'$item'" << '
done
The excludelist can be updated by running the bundled script generate-excludelist.sh
*/
QStringList excludelist;
excludelist << "libasound.so.2" << "libcom_err.so.2" << "libcrypt.so.1" << "libc.so.6" << "libdl.so.2" << "libdrm.so.2" << "libexpat.so.1" << "libfontconfig.so.1" << "libgcc_s.so.1" << "libgdk_pixbuf-2.0.so.0" << "libgdk-x11-2.0.so.0" << "libgio-2.0.so.0" << "libglib-2.0.so.0" << "libGL.so.1" << "libgobject-2.0.so.0" << "libgpg-error.so.0" << "libgssapi_krb5.so.2" << "libgtk-x11-2.0.so.0" << "libICE.so.6" << "libidn.so.11" << "libk5crypto.so.3" << "libkeyutils.so.1" << "libm.so.6" << "libnss3.so" << "libnssutil3.so" << "libp11-kit.so.0" << "libpangoft2-1.0.so.0" << "libpangocairo-1.0.so.0" << "libpango-1.0.so.0" << "libpthread.so.0" << "libresolv.so.2" << "librt.so.1" << "libSM.so.6" << "libstdc++.so.6" << "libusb-1.0.so.0" << "libuuid.so.1" << "libX11.so.6" << "libxcb.so.1" << "libz.so.1";
// copy generated excludelist
QStringList excludelist = generatedExcludelist;
// append exclude libs
excludelist += excludeLibs;
LogDebug() << "excludelist:" << excludelist;
if (! trimmed.contains("libicu")) {
if (containsHowOften(excludelist, QFileInfo(trimmed).completeBaseName())) {
@ -575,10 +636,13 @@ QList<LibraryInfo> getQtLibrariesForPaths(const QStringList &paths, const QStrin
QSet<QString> existing;
foreach (const QString &path, paths) {
foreach (const LibraryInfo &info, getQtLibraries(path, appDirPath, rpaths)) {
if (!existing.contains(info.libraryPath)) { // avoid duplicates
existing.insert(info.libraryPath);
result << info;
if (!excludeLibs.contains(QFileInfo(path).baseName()))
{
foreach (const LibraryInfo &info, getQtLibraries(path, appDirPath, rpaths)) {
if (!existing.contains(info.libraryPath)) { // avoid duplicates
existing.insert(info.libraryPath);
result << info;
}
}
}
}
@ -599,6 +663,8 @@ bool recursiveCopy(const QString &sourcePath, const QString &destinationPath)
const QString fileSourcePath = sourcePath + "/" + file;
const QString fileDestinationPath = destinationPath + "/" + file;
copyFilePrintStatus(fileSourcePath, fileDestinationPath);
LogDebug() << "copyCopyrightFile:" << fileSourcePath;
copyCopyrightFile(fileSourcePath);
}
QStringList subdirs = QDir(sourcePath).entryList(QStringList() << "*", QDir::Dirs | QDir::NoDotAndDotDot);
@ -620,6 +686,8 @@ void recursiveCopyAndDeploy(const QString &appDirPath, const QSet<QString> &rpat
QString fileDestinationPath = destinationPath + QLatin1Char('/') + file;
copyFilePrintStatus(fileSourcePath, fileDestinationPath);
LogDebug() << "copyCopyrightFile:" << fileSourcePath;
copyCopyrightFile(fileSourcePath);
if(fileDestinationPath.endsWith(".so")){
@ -675,6 +743,8 @@ QString copyDylib(const LibraryInfo &library, const QString path)
// Copy dylib binary
copyFilePrintStatus(library.sourceFilePath, dylibDestinationBinaryPath);
LogDebug() << "copyCopyrightFile:" << library.sourceFilePath;
copyCopyrightFile(library.sourceFilePath);
return dylibDestinationBinaryPath;
}
@ -920,13 +990,12 @@ DeploymentInfo deployQtLibraries(QList<LibraryInfo> libraries,
deploymentInfo.qtPath = library.libraryDirectory;
}
if(library.libraryName.contains("libQt") and library.libraryName.contains("Widgets.so")) {
if(library.libraryName.contains("libQt") and library.libraryName.contains("Widgets.so")) {
deploymentInfo.requiresQtWidgetsLibrary = true;
}
if (library.libraryDirectory.startsWith(bundlePath)) {
LogNormal() << library.libraryName << "already deployed, skipping.";
continue;
LogNormal() << library.libraryName << "already at target location";
}
if (library.rpathUsed.isEmpty() != true) {
@ -1071,9 +1140,16 @@ DeploymentInfo deployQtLibraries(const QString &appDirPath, const QStringList &a
} else {
libraryPath = QFileInfo(applicationBundle.binaryPath).dir().filePath("../lib/" + bundleLibraryDirectory);
}
foreach (const QString &executable, QStringList() << applicationBundle.binaryPath << additionalExecutables) {
changeIdentification("$ORIGIN/" + QFileInfo(executable).dir().relativeFilePath(libraryPath) + "/" + bundleLibraryDirectory, QFileInfo(executable).canonicalFilePath());
}
/* Make ldd detect pre-existing libraries in the AppDir.
* TODO: Consider searching the AppDir for .so* files outside of libraryPath
* and warning about them not being taken into consideration */
QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
QString oldPath = env.value("LD_LIBRARY_PATH");
QString newPath = libraryPath + ":" + oldPath; // FIXME: If we use a ldd replacement, we still need to observe this path
LogDebug() << "Changed LD_LIBRARY_PATH:" << newPath;
setenv("LD_LIBRARY_PATH",newPath.toUtf8().constData(),1);
applicationBundle.libraryPaths = findAppLibraries(appDirPath);
LogDebug() << "applicationBundle.libraryPaths:" << applicationBundle.libraryPaths;
@ -1088,17 +1164,24 @@ DeploymentInfo deployQtLibraries(const QString &appDirPath, const QStringList &a
LogDebug() << "allRPaths:" << allRPaths;
QList<LibraryInfo> libraries = getQtLibrariesForPaths(allBinaryPaths, appDirPath, allRPaths);
DeploymentInfo depInfo;
if (libraries.isEmpty() && !alwaysOwerwriteEnabled) {
LogWarning() << "Could not find any external Qt libraries to deploy in" << appDirPath;
LogWarning() << "Perhaps linuxdeployqt was already used on" << appDirPath << "?";
LogWarning() << "If so, you will need to rebuild" << appDirPath << "before trying again.";
LogWarning() << "Or ldd does not find the external Qt libraries but sees the system ones.";
LogWarning() << "If so, you will need to set LD_LIBRARY_PATH to the directory containing the external Qt libraries before trying again.";
LogWarning() << "FIXME: https://github.com/probonopd/linuxdeployqt/issues/2";
return DeploymentInfo();
LogWarning() << "Could not find any external Qt libraries to deploy in" << appDirPath;
LogWarning() << "Perhaps linuxdeployqt was already used on" << appDirPath << "?";
LogWarning() << "If so, you will need to rebuild" << appDirPath << "before trying again.";
LogWarning() << "Or ldd does not find the external Qt libraries but sees the system ones.";
LogWarning() << "If so, you will need to set LD_LIBRARY_PATH to the directory containing the external Qt libraries before trying again.";
LogWarning() << "FIXME: https://github.com/probonopd/linuxdeployqt/issues/2";
} else {
return deployQtLibraries(libraries, applicationBundle.path, allBinaryPaths, !additionalExecutables.isEmpty());
depInfo = deployQtLibraries(libraries, applicationBundle.path, allBinaryPaths, !additionalExecutables.isEmpty());
}
foreach (const QString &executable, QStringList() << applicationBundle.binaryPath << additionalExecutables) {
changeIdentification("$ORIGIN/" + QFileInfo(executable).dir().relativeFilePath(libraryPath), QFileInfo(executable).canonicalFilePath());
}
return depInfo;
}
void deployPlugins(const AppDirInfo &appDirInfo, const QString &pluginSourcePath,
@ -1118,6 +1201,20 @@ void deployPlugins(const AppDirInfo &appDirInfo, const QString &pluginSourcePath
if (containsHowOften(deploymentInfo.deployedLibraries, "libQt5Gui")) {
LogDebug() << "libQt5Gui detected";
pluginList.append("platforms/libqxcb.so");
// Platform plugin contexts - apparently needed to enter special characters
QStringList platformPluginContexts = QDir(pluginSourcePath + QStringLiteral("/platforminputcontexts")).entryList(QStringList() << QStringLiteral("*.so"));
foreach (const QString &plugin, platformPluginContexts) {
pluginList.append(QStringLiteral("platforminputcontexts/") + plugin);
}
// Platform themes - make Qt look more native e.g., on Gtk+ 3 (if available in Qt installation)
// FIXME: Do not do this until we find a good way to do this without also deploying their dependencies
// See https://github.com/probonopd/linuxdeployqt/issues/236
/*
QStringList platformThemes = QDir(pluginSourcePath + QStringLiteral("/platformthemes")).entryList(QStringList() << QStringLiteral("*.so"));
foreach (const QString &plugin, platformThemes) {
pluginList.append(QStringLiteral("platformthemes/") + plugin);
}
*/
// All image formats (svg if QtSvg library is used)
QStringList imagePlugins = QDir(pluginSourcePath + QStringLiteral("/imageformats")).entryList(QStringList() << QStringLiteral("*.so"));
foreach (const QString &plugin, imagePlugins) {
@ -1132,7 +1229,9 @@ void deployPlugins(const AppDirInfo &appDirInfo, const QString &pluginSourcePath
}
// Platform OpenGL context
if ((containsHowOften(deploymentInfo.deployedLibraries, "libQt5OpenGL")) or (containsHowOften(deploymentInfo.deployedLibraries, "libQt5XcbQpa"))) {
if ((containsHowOften(deploymentInfo.deployedLibraries, "libQt5OpenGL"))
or (containsHowOften(deploymentInfo.deployedLibraries, "libQt5XcbQpa"))
or (containsHowOften(deploymentInfo.deployedLibraries, "libxcb-glx"))) {
QStringList xcbglintegrationPlugins = QDir(pluginSourcePath + QStringLiteral("/xcbglintegrations")).entryList(QStringList() << QStringLiteral("*.so"));
foreach (const QString &plugin, xcbglintegrationPlugins) {
pluginList.append(QStringLiteral("xcbglintegrations/") + plugin);
@ -1166,6 +1265,14 @@ void deployPlugins(const AppDirInfo &appDirInfo, const QString &pluginSourcePath
}
}
// Positioning plugins if QtPositioning library is in use
if (containsHowOften(deploymentInfo.deployedLibraries, "libQt5Positioning")) {
QStringList posPlugins = QDir(pluginSourcePath + QStringLiteral("/position")).entryList(QStringList() << QStringLiteral("*.so"));
foreach (const QString &plugin, posPlugins) {
pluginList.append(QStringLiteral("position/") + plugin);
}
}
// multimedia plugins if QtMultimedia library is in use
if (containsHowOften(deploymentInfo.deployedLibraries, "libQt5Multimedia")) {
QStringList plugins = QDir(pluginSourcePath + QStringLiteral("/mediaservice")).entryList(QStringList() << QStringLiteral("*.so"));
@ -1238,25 +1345,60 @@ void deployPlugins(const AppDirInfo &appDirInfo, const QString &pluginSourcePath
recursiveCopy(sourcePath, destinationPath);
}
if (!extraQtPlugins.isEmpty()) {
LogNormal() << "Deploying extra plugins.";
foreach (const QString &plugin, extraQtPlugins) {
QDir pluginDirectory(pluginSourcePath + "/" + plugin);
if (pluginDirectory.exists()) {
//If it is a plugin directory we will deploy the entire directory
QStringList plugins = pluginDirectory.entryList(QStringList() << QStringLiteral("*.so"));
foreach (const QString &pluginFile, plugins) {
pluginList.append(plugin + "/" + pluginFile);
LogDebug() << plugin + "/" + pluginFile << "appended";
}
}
else {
//If it isn't a directory we asume it is an explicit plugin and we will try to deploy that
if (!pluginList.contains(plugin)) {
if (QFile::exists(pluginSourcePath + "/" + plugin)) {
pluginList.append(plugin);
LogDebug() << plugin << "appended";
}
else {
LogDebug() << "The plugin" << plugin << "was already deployed." ;
}
}
else {
LogWarning() << "The plugin" << pluginSourcePath + "/" + plugin << "could not be found. Please check spelling and try again!";
}
}
}
}
LogNormal() << "pluginList after having detected hopefully all required plugins:" << pluginList;
foreach (const QString &plugin, pluginList) {
sourcePath = pluginSourcePath + "/" + plugin;
destinationPath = pluginDestinationPath + "/" + plugin;
QDir dir;
dir.mkpath(QFileInfo(destinationPath).path());
QList<LibraryInfo> libraries = getQtLibraries(sourcePath, appDirInfo.path, deploymentInfo.rpathsUsed);
LogDebug() << "Deploying plugin" << sourcePath;
if (copyFilePrintStatus(sourcePath, destinationPath)) {
runStrip(destinationPath);
deployQtLibraries(libraries, appDirInfo.path, QStringList() << destinationPath, deploymentInfo.useLoaderPath);
/* See whether this makes any difference */
// Find out the relative path to the lib/ directory and set it as the rpath
QDir dir(destinationPath);
QString relativePath = dir.relativeFilePath(appDirInfo.path + "/" + libraries[0].libraryDestinationDirectory);
relativePath.remove(0, 3); // remove initial '../'
changeIdentification("$ORIGIN/" + relativePath, QFileInfo(destinationPath).canonicalFilePath());
if(!excludeLibs.contains(QFileInfo(sourcePath).baseName()))
{
QDir dir;
dir.mkpath(QFileInfo(destinationPath).path());
QList<LibraryInfo> libraries = getQtLibraries(sourcePath, appDirInfo.path, deploymentInfo.rpathsUsed);
LogDebug() << "Deploying plugin" << sourcePath;
if (copyFilePrintStatus(sourcePath, destinationPath)) {
runStrip(destinationPath);
deployQtLibraries(libraries, appDirInfo.path, QStringList() << destinationPath, deploymentInfo.useLoaderPath);
/* See whether this makes any difference */
// Find out the relative path to the lib/ directory and set it as the rpath
QDir dir(destinationPath);
QString relativePath = dir.relativeFilePath(appDirInfo.path + "/" + libraries[0].libraryDestinationDirectory);
relativePath.remove(0, 3); // remove initial '../'
changeIdentification("$ORIGIN/" + relativePath, QFileInfo(destinationPath).canonicalFilePath());
}
LogDebug() << "copyCopyrightFile:" << sourcePath;
copyCopyrightFile(sourcePath);
}
}
}
@ -1571,7 +1713,7 @@ bool checkAppImagePrerequisites(const QString &appDirPath)
int createAppImage(const QString &appDirPath)
{
QString appImageCommand = "appimagetool '" + appDirPath + "' --verbose -n"; // +"' '" + appImagePath + "'";
QString appImageCommand = "appimagetool '" + appDirPath + "' --verbose -n -g"; // +"' '" + appImagePath + "'";
int ret = system(appImageCommand.toUtf8().constData());
LogNormal() << "ret" << ret;
LogNormal() << "WEXITSTATUS(ret)" << WEXITSTATUS(ret);
@ -1613,9 +1755,18 @@ void deployTranslations(const QString &appDirPath, quint64 usedQtModules)
return;
}
QString translationsDirPath = appDirPath + QStringLiteral("/translations");
LogDebug() << "Using" << translationsDirPath << "as translations directory for App";
LogDebug() << "Using" << qtTranslationsPath << " to search for Qt translations";
QString translationsDirPath;
if (!fhsLikeMode) {
translationsDirPath = appDirPath + QStringLiteral("/translations");
} else {
// TODO: refactor this global variables hack
QFileInfo appBinaryFI(appBinaryPath);
QString appRoot = appBinaryFI.absoluteDir().absolutePath() + "/../";
translationsDirPath = appRoot + QStringLiteral("/translations");
}
LogNormal() << "Using" << translationsDirPath << "as translations directory for App";
LogNormal() << "Using" << qtTranslationsPath << " to search for Qt translations";
QFileInfo fi(translationsDirPath);
if (!fi.isDir()) {

4
tools/linuxdeployqt/shared.h

@ -1,6 +1,6 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd. and Simon Peter
** Copyright (C) 2016-18 The Qt Company Ltd. and Simon Peter
** Contact: https://www.qt.io/licensing/
**
** This file is part of the tools applications of the Qt Toolkit.
@ -45,6 +45,8 @@ extern bool runStripEnabled;
extern bool bundleAllButCoreLibs;
extern bool fhsLikeMode;
extern QString fhsPrefix;
extern QStringList extraQtPlugins;
extern QStringList excludeLibs;
class LibraryInfo
{

Loading…
Cancel
Save