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. 97
      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. 32
      tools/linuxdeployqt/linuxdeployqt.pro
  15. 128
      tools/linuxdeployqt/main.cpp
  16. 255
      tools/linuxdeployqt/shared.cpp
  17. 4
      tools/linuxdeployqt/shared.h

1
.gitignore

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

8
.travis.yml

@ -11,12 +11,18 @@ env:
before_install: before_install:
- ./tests/tests-environment.sh - ./tests/tests-environment.sh
before_script:
# fetch all tags
- git fetch --unshallow
script: script:
- ./tests/tests-ci.sh - ./tests/tests-ci.sh
after_success: after_success:
- wget -c https://github.com/probonopd/uploadtool/raw/master/upload.sh - 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: after_script:
- "xpra stop :99" - "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 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/ Contact: http://www.qt.io/licensing/
You may use, distribute and copy the Qt Toolkit under the terms of 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 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/ Contact: http://www.qt.io/licensing/
You may use, distribute and copy the Qt Toolkit under the terms of You may use, distribute and copy the Qt Toolkit under the terms of

97
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) # 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 ## 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. 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.
@ -32,6 +34,7 @@ Options:
-always-overwrite : Copy files even if the target file exists -always-overwrite : Copy files even if the target file exists
-qmake=<path> : The qmake executable to use -qmake=<path> : The qmake executable to use
-no-translations : Skip deployment of translations -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 linuxdeployqt takes an application as input and makes it
self-contained by copying in the Qt libraries and plugins that self-contained by copying in the Qt libraries and plugins that
@ -40,12 +43,43 @@ the application uses.
#### Simplest example #### 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.
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.
`linuxdeployqt ./path/to/appdir/usr/share/application_name.desktop` Read more about desktop files in the [freedesktop specification here](https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html).
Where the _desktop_ file specifies the executable to be run (with `EXEC=`), the name of the applications and an icon. Now you can say: `linuxdeployqt-continuous-x86_64.AppImage path/to/AppDir/usr/share/applications/your_app.desktop`
See [desktop file specification](https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html).
For a more detailed example, see "Using linuxdeployqt with Travis CI" below. 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`. 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 ## 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): 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,7 +135,7 @@ sudo: require
dist: trusty dist: trusty
before_install: before_install:
- sudo add-apt-repository ppa:beineri/opt-qt59-trusty -y - sudo add-apt-repository ppa:beineri/opt-qt593-trusty -y
- sudo apt-get update -qq - sudo apt-get update -qq
install: install:
@ -100,15 +146,23 @@ script:
- qmake CONFIG+=release PREFIX=/usr - qmake CONFIG+=release PREFIX=/usr
- make -j$(nproc) - make -j$(nproc)
- make INSTALL_ROOT=appdir -j$(nproc) install ; find appdir/ - make INSTALL_ROOT=appdir -j$(nproc) install ; find appdir/
- wget -c "https://github.com/probonopd/linuxdeployqt/releases/download/continuous/linuxdeployqt-continuous-x86_64.AppImage" - wget -c -nv "https://github.com/probonopd/linuxdeployqt/releases/download/continuous/linuxdeployqt-continuous-x86_64.AppImage"
- chmod a+x linuxdeployqt*.AppImage - chmod a+x linuxdeployqt-continuous-x86_64.AppImage
- unset QTDIR; unset QT_PLUGIN_PATH ; unset LD_LIBRARY_PATH - unset QTDIR; unset QT_PLUGIN_PATH ; unset LD_LIBRARY_PATH
- ./linuxdeployqt*.AppImage ./appdir/usr/share/applications/*.desktop -bundle-non-qt-libs - export VERSION=$(git rev-parse --short HEAD) # linuxdeployqt uses this for naming the file
- ./linuxdeployqt*.AppImage ./appdir/usr/share/applications/*.desktop -appimage - ./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: after_success:
- find ./appdir -executable -type f -exec ldd {} \; | grep " => /usr" | cut -d " " -f 2-3 | sort | uniq - 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 - # 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. 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. 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/ - make INSTALL_ROOT=appdir install ; find appdir/
``` ```
@ -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. `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). 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.
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.
Providing an [AppImage](http://appimage.org/) would have, among others, these advantages: 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) - 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) - Can optionally GPG2-sign your AppImages (inside the file)
- Works on Live ISOs - Works on Live ISOs
- Can use the same AppImages when dual-booting multiple distributions - 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 - 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
[Here is an overview](https://github.com/probonopd/AppImageKit/wiki/AppImages) of projects that are already distributing upstream-provided, official AppImages.
__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 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. 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 ## Projects using linuxdeployqt
These projects are already using [Travis CI](http://travis-ci.org/) and linuxdeployqt to provide AppImages of their builds: 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://github.com/jimevins/glabels-qt
- https://travis-ci.org/NeoTheFox/RepRaptor - https://travis-ci.org/NeoTheFox/RepRaptor
- https://github.com/electronpass/electronpass-desktop - 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/probonopd/linuxdeployqt/ obviously ;-)
- https://github.com/xdgurl/xdgurl - https://github.com/xdgurl/xdgurl
- https://github.com/QNapi/qnapi - 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: This project is already using linuxdeployqt in a custom Jenkins workflow:
- https://github.com/appimage-packages/ - 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/ ** Contact: http://www.qt.io/licensing/
** **
** This file is part of the examples of the Qt Toolkit. ** 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/ ** Contact: http://www.qt.io/licensing/
** **
** This file is part of the examples of the Qt Toolkit. ** 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 /opt/qt*/bin/qmake CONFIG+=release CONFIG+=force_debug_info linuxdeployqt.pro
make -j make -j
mkdir -p linuxdeployqt.AppDir/usr/bin/ # exit on failure
cp /usr/bin/patchelf /usr/local/bin/{appimagetool,mksquashfs,zsyncmake} linuxdeployqt.AppDir/usr/bin/ 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/ find linuxdeployqt.AppDir/
export VERSION=continuous 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 ./bin/linuxdeployqt linuxdeployqt.AppDir/linuxdeployqt.desktop -verbose=3 -appimage
ls -lh ls -lh
find *.AppDir find *.AppDir
@ -31,9 +36,18 @@ ulimit -c unlimited
ulimit -a -S ulimit -a -S
ulimit -a -H 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" echo "FAILURE: linuxdeployqt CRASHED -- uploading files for debugging to transfer.sh"
set -v set -v
[ -e /tmp/coredump ] && curl --upload-file /tmp/coredump https://transfer.sh/coredump [ -e /tmp/coredump ] && curl --upload-file /tmp/coredump https://transfer.sh/coredump

13
tests/tests-environment.sh

@ -2,17 +2,20 @@
set -e 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 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 sudo dpkg -i patchelf_0.8-2_amd64.deb
cd /tmp/ 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 chmod +x appimagetool*AppImage
./appimagetool*AppImage --appimage-extract ./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 - 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"
};

32
tools/linuxdeployqt/linuxdeployqt.pro

@ -15,3 +15,35 @@ SOURCES += main.cpp \
shared.cpp shared.cpp
DEFINES -= QT_USE_QSTRINGBUILDER #leads to compile errors if not disabled 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")
}
}

128
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/ ** Contact: https://www.qt.io/licensing/
** **
** This file is part of the tools applications of the Qt Toolkit. ** This file is part of the tools applications of the Qt Toolkit.
@ -33,6 +33,8 @@
#include <stdlib.h> #include <stdlib.h>
#include <QSettings> #include <QSettings>
#include <QDirIterator> #include <QDirIterator>
#include <sstream>
#include "excludelist.h"
int main(int argc, char **argv) int main(int argc, char **argv)
{ {
@ -43,37 +45,69 @@ int main(int argc, char **argv)
QString firstArgument = QString::fromLocal8Bit(argv[1]); QString firstArgument = QString::fromLocal8Bit(argv[1]);
if (argc < 2 || firstArgument.startsWith("-")) { // print version statement
qDebug() << "Usage: linuxdeployqt <app-binary|desktop file> [options]"; std::stringstream version;
qDebug() << ""; version << "linuxdeployqt " << LINUXDEPLOYQT_VERSION
qDebug() << "Options:"; << " (commit " << LINUXDEPLOYQT_GIT_COMMIT << "), "
qDebug() << " -verbose=<0-3> : 0 = no output, 1 = error/warning (default), 2 = normal, 3 = debug"; << "build " << BUILD_NUMBER << " built on " << BUILD_DATE;
qDebug() << " -no-plugins : Skip plugin deployment"; qInfo().noquote() << QString::fromStdString(version.str());
qDebug() << " -appimage : Create an AppImage (implies -bundle-non-qt-libs)";
qDebug() << " -no-strip : Don't run 'strip' on the binaries"; // due to the structure of the argument parser, we have to check all arguments at first to check whether the user
qDebug() << " -bundle-non-qt-libs : Also bundle non-core, non-Qt libraries"; // wants to get the version only
qDebug() << " -executable=<path> : Let the given executable use the deployed libraries too"; // TODO: replace argument parser with position independent, less error prone version
qDebug() << " -qmldir=<path> : Scan for QML imports in the given path"; for (int i = 0; i < argc; i++ ) {
qDebug() << " -always-overwrite : Copy files even if the target file exists"; QString argument = argv[i];
qDebug() << " -qmake=<path> : The qmake executable to use"; if (argument == "-version" || argument == "-V" || argument == "--version") {
qDebug() << " -no-translations : Skip deployment of translations."; // can just exit normally, version has been printed above
qDebug() << ""; return 0;
qDebug() << "linuxdeployqt takes an application as input and makes it"; }
qDebug() << "self-contained by copying in the Qt libraries and plugins that"; if (argument == QByteArray("-show-exclude-libs")) {
qDebug() << "the application uses."; qInfo() << generatedExcludelist;
qDebug() << ""; return 0;
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() << ""; if (argc < 2 || (firstArgument.startsWith("-"))) {
qDebug() << "Plugins related to a Qt library are copied in with the library."; 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 /* TODO: To be implemented
qDebug() << "The accessibility, image formats, and text codec"; qDebug() << "The accessibility, image formats, and text codec";
qDebug() << "plugins are always copied, unless \"-no-plugins\" is specified."; qDebug() << "plugins are always copied, unless \"-no-plugins\" is specified.";
*/ */
qDebug() << ""; qInfo() << "";
qDebug() << "See the \"Deploying Applications on Linux\" topic in the"; qInfo() << "See the \"Deploying Applications on Linux\" topic in the";
qDebug() << "documentation for more information about deployment on Linux."; qInfo() << "documentation for more information about deployment on Linux.";
return 1; return 1;
} }
@ -88,6 +122,12 @@ int main(int argc, char **argv)
* to do when using linuxdeployqt. */ * to do when using linuxdeployqt. */
if (firstArgument.endsWith(".desktop")){ if (firstArgument.endsWith(".desktop")){
qDebug() << "Desktop file as first argument:" << firstArgument; 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; QSettings * settings = 0;
settings = new QSettings(firstArgument, QSettings::IniFormat); settings = new QSettings(firstArgument, QSettings::IniFormat);
desktopExecEntry = settings->value("Desktop Entry/Exec", "r").toString().split(' ').first().split('/').last().trimmed(); 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 bundleAllButCoreLibs;
extern bool fhsLikeMode; extern bool fhsLikeMode;
extern QString fhsPrefix; extern QString fhsPrefix;
extern bool alwaysOwerwriteEnabled;
extern QStringList librarySearchPath; extern QStringList librarySearchPath;
extern bool alwaysOwerwriteEnabled;
QStringList additionalExecutables; QStringList additionalExecutables;
bool qmldirArgumentUsed = false; bool qmldirArgumentUsed = false;
bool skipTranslations = false; bool skipTranslations = false;
QStringList qmlDirs; QStringList qmlDirs;
QString qmakeExecutable; 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. /* 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. */ * 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){ if(QFileInfo(appDirPath + "/" + desktopIconEntry + ".svgz").exists() == true){
preExistingToplevelIcon = appDirPath + "/" + desktopIconEntry + ".svgz"; 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){ if(QFileInfo(appDirPath + "/" + desktopIconEntry + ".svg").exists() == true){
preExistingToplevelIcon = appDirPath + "/" + desktopIconEntry + ".svg"; preExistingToplevelIcon = appDirPath + "/" + desktopIconEntry + ".svg";
@ -326,8 +369,10 @@ int main(int argc, char **argv)
} }
} }
// Check arguments
for (int i = 2; i < argc; ++i) { for (int i = 2; i < argc; ++i) {
QByteArray argument = QByteArray(argv[i]); QByteArray argument = QByteArray(argv[i]);
if (argument == QByteArray("-no-plugins")) { if (argument == QByteArray("-no-plugins")) {
LogDebug() << "Argument found:" << argument; LogDebug() << "Argument found:" << argument;
plugins = false; plugins = false;
@ -365,6 +410,9 @@ int main(int argc, char **argv)
LogError() << "Missing qml directory path"; LogError() << "Missing qml directory path";
else else
qmlDirs << argument.mid(index+1); 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")) { } else if (argument == QByteArray("-always-overwrite")) {
LogDebug() << "Argument found:" << argument; LogDebug() << "Argument found:" << argument;
alwaysOwerwriteEnabled = true; alwaysOwerwriteEnabled = true;
@ -375,8 +423,19 @@ int main(int argc, char **argv)
} else if (argument == QByteArray("-no-translations")) { } else if (argument == QByteArray("-no-translations")) {
LogDebug() << "Argument found:" << argument; LogDebug() << "Argument found:" << argument;
skipTranslations = true; skipTranslations = true;
} else if (argument.startsWith("-")) { } else if (argument.startsWith("-extra-plugins=")) {
LogError() << "Unknown argument" << argument << "\n"; 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; return 1;
} }
} }
@ -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, DeploymentInfo deploymentInfo = deployQtLibraries(appDirPath, additionalExecutables,
qmakeExecutable); qmakeExecutable);

255
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/ ** Contact: https://www.qt.io/licensing/
** **
** This file is part of the tools applications of the Qt Toolkit. ** This file is part of the tools applications of the Qt Toolkit.
@ -44,6 +44,7 @@
#include <QRegularExpression> #include <QRegularExpression>
#include <QStandardPaths> #include <QStandardPaths>
#include "shared.h" #include "shared.h"
#include "excludelist.h"
QString appBinaryPath; QString appBinaryPath;
bool runStripEnabled = true; bool runStripEnabled = true;
@ -57,6 +58,9 @@ int logLevel = 1;
int qtDetected = 0; int qtDetected = 0;
bool qtDetectionComplete = 0; // As long as Qt is not detected yet, ldd may encounter "not found" messages, continue anyway bool qtDetectionComplete = 0; // As long as Qt is not detected yet, ldd may encounter "not found" messages, continue anyway
bool deployLibrary = false; bool deployLibrary = false;
QStringList extraQtPlugins;
QStringList excludeLibs;
bool copyCopyrightFiles = true;
using std::cout; using std::cout;
using std::endl; using std::endl;
@ -212,36 +216,20 @@ inline QDebug operator<<(QDebug debug, const AppDirInfo &info)
return debug; 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) bool copyFilePrintStatus(const QString &from, const QString &to)
{ {
if (QFile(to).exists()) { if (QFile(to).exists()) {
if (alwaysOwerwriteEnabled) { if (alwaysOwerwriteEnabled) {
QFile(to).remove(); QFile(to).remove();
} else { } else {
LogDebug() << QFileInfo(to).fileName() << "already deployed, skipping."; LogDebug() << QFileInfo(to).fileName() << "already exists at target location";
return false; return true;
}
} }
QDir dir(QDir::cleanPath(to + "/../"));
if (!dir.exists()) {
dir.mkpath(".");
} }
if (QFile::copy(from, to)) { if (QFile::copy(from, to)) {
@ -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 findDependencyInfo(const QString &binaryPath)
{ {
LddInfo info; LddInfo info;
@ -309,20 +372,19 @@ LddInfo findDependencyInfo(const QString &binaryPath)
// LogDebug() << "ldd outputLine:" << outputLine; // LogDebug() << "ldd outputLine:" << outputLine;
if ((outputLine.contains("not found")) && (qtDetectionComplete == 1)){ if ((outputLine.contains("not found")) && (qtDetectionComplete == 1)){
LogError() << "ldd outputLine:" << outputLine.replace("\t", ""); LogError() << "ldd outputLine:" << outputLine.replace("\t", "");
LogError() << "for binary:" << binaryPath;
LogError() << "Please ensure that all libraries can be found by ldd. Aborting."; LogError() << "Please ensure that all libraries can be found by ldd. Aborting.";
exit(1); exit(1);
} }
} }
if ((binaryPath.contains(".so.") || binaryPath.endsWith(".so")) && (!lddOutputContainsLinuxVDSO(output))) { /*
const QRegularExpressionMatch match = regexp.match(outputLines.first()); FIXME: For unknown reasons, this segfaults; see https://travis-ci.org/probonopd/Labrador/builds/339803886#L1320
if (match.hasMatch()) { if (binaryPath.contains("platformthemes")) {
info.installName = match.captured(1); LogDebug() << "Not adding dependencies of" << binaryPath << "because we do not bundle dependencies of platformthemes";
} else { return info;
LogError() << "Could not parse ldd output line:" << outputLines.first();
}
outputLines.removeFirst();
} }
*/
foreach (const QString &outputLine, outputLines) { foreach (const QString &outputLine, outputLines) {
const QRegularExpressionMatch match = regexp.match(outputLine); 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. 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. Along the way, this also takes care of non-Qt libraries.
The excludelist can be updated by running The excludelist can be updated by running the bundled script generate-excludelist.sh
#/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
*/ */
QStringList excludelist; // copy generated 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"; QStringList excludelist = generatedExcludelist;
// append exclude libs
excludelist += excludeLibs;
LogDebug() << "excludelist:" << excludelist; LogDebug() << "excludelist:" << excludelist;
if (! trimmed.contains("libicu")) { if (! trimmed.contains("libicu")) {
if (containsHowOften(excludelist, QFileInfo(trimmed).completeBaseName())) { if (containsHowOften(excludelist, QFileInfo(trimmed).completeBaseName())) {
@ -575,6 +636,8 @@ QList<LibraryInfo> getQtLibrariesForPaths(const QStringList &paths, const QStrin
QSet<QString> existing; QSet<QString> existing;
foreach (const QString &path, paths) { foreach (const QString &path, paths) {
if (!excludeLibs.contains(QFileInfo(path).baseName()))
{
foreach (const LibraryInfo &info, getQtLibraries(path, appDirPath, rpaths)) { foreach (const LibraryInfo &info, getQtLibraries(path, appDirPath, rpaths)) {
if (!existing.contains(info.libraryPath)) { // avoid duplicates if (!existing.contains(info.libraryPath)) { // avoid duplicates
existing.insert(info.libraryPath); existing.insert(info.libraryPath);
@ -582,6 +645,7 @@ QList<LibraryInfo> getQtLibrariesForPaths(const QStringList &paths, const QStrin
} }
} }
} }
}
return result; return result;
} }
@ -599,6 +663,8 @@ bool recursiveCopy(const QString &sourcePath, const QString &destinationPath)
const QString fileSourcePath = sourcePath + "/" + file; const QString fileSourcePath = sourcePath + "/" + file;
const QString fileDestinationPath = destinationPath + "/" + file; const QString fileDestinationPath = destinationPath + "/" + file;
copyFilePrintStatus(fileSourcePath, fileDestinationPath); copyFilePrintStatus(fileSourcePath, fileDestinationPath);
LogDebug() << "copyCopyrightFile:" << fileSourcePath;
copyCopyrightFile(fileSourcePath);
} }
QStringList subdirs = QDir(sourcePath).entryList(QStringList() << "*", QDir::Dirs | QDir::NoDotAndDotDot); 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; QString fileDestinationPath = destinationPath + QLatin1Char('/') + file;
copyFilePrintStatus(fileSourcePath, fileDestinationPath); copyFilePrintStatus(fileSourcePath, fileDestinationPath);
LogDebug() << "copyCopyrightFile:" << fileSourcePath;
copyCopyrightFile(fileSourcePath);
if(fileDestinationPath.endsWith(".so")){ if(fileDestinationPath.endsWith(".so")){
@ -675,6 +743,8 @@ QString copyDylib(const LibraryInfo &library, const QString path)
// Copy dylib binary // Copy dylib binary
copyFilePrintStatus(library.sourceFilePath, dylibDestinationBinaryPath); copyFilePrintStatus(library.sourceFilePath, dylibDestinationBinaryPath);
LogDebug() << "copyCopyrightFile:" << library.sourceFilePath;
copyCopyrightFile(library.sourceFilePath);
return dylibDestinationBinaryPath; return dylibDestinationBinaryPath;
} }
@ -925,8 +995,7 @@ DeploymentInfo deployQtLibraries(QList<LibraryInfo> libraries,
} }
if (library.libraryDirectory.startsWith(bundlePath)) { if (library.libraryDirectory.startsWith(bundlePath)) {
LogNormal() << library.libraryName << "already deployed, skipping."; LogNormal() << library.libraryName << "already at target location";
continue;
} }
if (library.rpathUsed.isEmpty() != true) { if (library.rpathUsed.isEmpty() != true) {
@ -1071,9 +1140,16 @@ DeploymentInfo deployQtLibraries(const QString &appDirPath, const QStringList &a
} else { } else {
libraryPath = QFileInfo(applicationBundle.binaryPath).dir().filePath("../lib/" + bundleLibraryDirectory); 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); applicationBundle.libraryPaths = findAppLibraries(appDirPath);
LogDebug() << "applicationBundle.libraryPaths:" << applicationBundle.libraryPaths; LogDebug() << "applicationBundle.libraryPaths:" << applicationBundle.libraryPaths;
@ -1088,6 +1164,8 @@ DeploymentInfo deployQtLibraries(const QString &appDirPath, const QStringList &a
LogDebug() << "allRPaths:" << allRPaths; LogDebug() << "allRPaths:" << allRPaths;
QList<LibraryInfo> libraries = getQtLibrariesForPaths(allBinaryPaths, appDirPath, allRPaths); QList<LibraryInfo> libraries = getQtLibrariesForPaths(allBinaryPaths, appDirPath, allRPaths);
DeploymentInfo depInfo;
if (libraries.isEmpty() && !alwaysOwerwriteEnabled) { if (libraries.isEmpty() && !alwaysOwerwriteEnabled) {
LogWarning() << "Could not find any external Qt libraries to deploy in" << appDirPath; LogWarning() << "Could not find any external Qt libraries to deploy in" << appDirPath;
LogWarning() << "Perhaps linuxdeployqt was already used on" << appDirPath << "?"; LogWarning() << "Perhaps linuxdeployqt was already used on" << appDirPath << "?";
@ -1095,10 +1173,15 @@ DeploymentInfo deployQtLibraries(const QString &appDirPath, const QStringList &a
LogWarning() << "Or ldd does not find the external Qt libraries but sees the system ones."; 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() << "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"; LogWarning() << "FIXME: https://github.com/probonopd/linuxdeployqt/issues/2";
return DeploymentInfo();
} else { } 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, 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")) { if (containsHowOften(deploymentInfo.deployedLibraries, "libQt5Gui")) {
LogDebug() << "libQt5Gui detected"; LogDebug() << "libQt5Gui detected";
pluginList.append("platforms/libqxcb.so"); 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) // All image formats (svg if QtSvg library is used)
QStringList imagePlugins = QDir(pluginSourcePath + QStringLiteral("/imageformats")).entryList(QStringList() << QStringLiteral("*.so")); QStringList imagePlugins = QDir(pluginSourcePath + QStringLiteral("/imageformats")).entryList(QStringList() << QStringLiteral("*.so"));
foreach (const QString &plugin, imagePlugins) { foreach (const QString &plugin, imagePlugins) {
@ -1132,7 +1229,9 @@ void deployPlugins(const AppDirInfo &appDirInfo, const QString &pluginSourcePath
} }
// Platform OpenGL context // 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")); QStringList xcbglintegrationPlugins = QDir(pluginSourcePath + QStringLiteral("/xcbglintegrations")).entryList(QStringList() << QStringLiteral("*.so"));
foreach (const QString &plugin, xcbglintegrationPlugins) { foreach (const QString &plugin, xcbglintegrationPlugins) {
pluginList.append(QStringLiteral("xcbglintegrations/") + plugin); 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 // multimedia plugins if QtMultimedia library is in use
if (containsHowOften(deploymentInfo.deployedLibraries, "libQt5Multimedia")) { if (containsHowOften(deploymentInfo.deployedLibraries, "libQt5Multimedia")) {
QStringList plugins = QDir(pluginSourcePath + QStringLiteral("/mediaservice")).entryList(QStringList() << QStringLiteral("*.so")); QStringList plugins = QDir(pluginSourcePath + QStringLiteral("/mediaservice")).entryList(QStringList() << QStringLiteral("*.so"));
@ -1238,11 +1345,43 @@ void deployPlugins(const AppDirInfo &appDirInfo, const QString &pluginSourcePath
recursiveCopy(sourcePath, destinationPath); 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; LogNormal() << "pluginList after having detected hopefully all required plugins:" << pluginList;
foreach (const QString &plugin, pluginList) { foreach (const QString &plugin, pluginList) {
sourcePath = pluginSourcePath + "/" + plugin; sourcePath = pluginSourcePath + "/" + plugin;
destinationPath = pluginDestinationPath + "/" + plugin; destinationPath = pluginDestinationPath + "/" + plugin;
if(!excludeLibs.contains(QFileInfo(sourcePath).baseName()))
{
QDir dir; QDir dir;
dir.mkpath(QFileInfo(destinationPath).path()); dir.mkpath(QFileInfo(destinationPath).path());
QList<LibraryInfo> libraries = getQtLibraries(sourcePath, appDirInfo.path, deploymentInfo.rpathsUsed); QList<LibraryInfo> libraries = getQtLibraries(sourcePath, appDirInfo.path, deploymentInfo.rpathsUsed);
@ -1258,6 +1397,9 @@ void deployPlugins(const AppDirInfo &appDirInfo, const QString &pluginSourcePath
changeIdentification("$ORIGIN/" + relativePath, QFileInfo(destinationPath).canonicalFilePath()); 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) 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()); int ret = system(appImageCommand.toUtf8().constData());
LogNormal() << "ret" << ret; LogNormal() << "ret" << ret;
LogNormal() << "WEXITSTATUS(ret)" << WEXITSTATUS(ret); LogNormal() << "WEXITSTATUS(ret)" << WEXITSTATUS(ret);
@ -1613,9 +1755,18 @@ void deployTranslations(const QString &appDirPath, quint64 usedQtModules)
return; return;
} }
QString translationsDirPath = appDirPath + QStringLiteral("/translations"); QString translationsDirPath;
LogDebug() << "Using" << translationsDirPath << "as translations directory for App"; if (!fhsLikeMode) {
LogDebug() << "Using" << qtTranslationsPath << " to search for Qt translations"; 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); QFileInfo fi(translationsDirPath);
if (!fi.isDir()) { 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/ ** Contact: https://www.qt.io/licensing/
** **
** This file is part of the tools applications of the Qt Toolkit. ** This file is part of the tools applications of the Qt Toolkit.
@ -45,6 +45,8 @@ extern bool runStripEnabled;
extern bool bundleAllButCoreLibs; extern bool bundleAllButCoreLibs;
extern bool fhsLikeMode; extern bool fhsLikeMode;
extern QString fhsPrefix; extern QString fhsPrefix;
extern QStringList extraQtPlugins;
extern QStringList excludeLibs;
class LibraryInfo class LibraryInfo
{ {

Loading…
Cancel
Save