deployqt.py 10.7 KB
Newer Older
1 2
#!/usr/bin/env python
################################################################################
hjk's avatar
hjk committed
3
# Copyright (C) 2012 Digia Plc
4 5 6 7 8 9 10 11 12 13
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
#   * Redistributions of source code must retain the above copyright notice,
#     this list of conditions and the following disclaimer.
#   * Redistributions in binary form must reproduce the above copyright notice,
#     this list of conditions and the following disclaimer in the documentation
#     and/or other materials provided with the distribution.
hjk's avatar
hjk committed
14
#   * Neither the name of Digia Plc, nor the names of its contributors
15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
#     may be used to endorse or promote products derived from this software
#     without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
################################################################################

import os
import sys
import getopt
import subprocess
import re
import string
import shutil
from glob import glob

ignoreErrors = False
40
debug_build = False
41 42

def usage():
43
    print "Usage: %s <creator_install_dir> [qmake_path]" % os.path.basename(sys.argv[0])
44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66

def which(program):
    def is_exe(fpath):
        return os.path.exists(fpath) and os.access(fpath, os.X_OK)

    fpath, fname = os.path.split(program)
    if fpath:
        if is_exe(program):
            return program
        if sys.platform.startswith('win'):
            if is_exe(program + ".exe"):
                return program  + ".exe"
    else:
        for path in os.environ["PATH"].split(os.pathsep):
            exe_file = os.path.join(path, program)
            if is_exe(exe_file):
                return exe_file
            if sys.platform.startswith('win'):
                if is_exe(exe_file + ".exe"):
                    return exe_file  + ".exe"

    return None

67
def is_debug(fpath):
68
    # bootstrap exception
69
    if fpath.endswith('QtCore4d.dll') or fpath.endswith('QtCore5d.dll'):
70 71
        return True
    output = subprocess.check_output(['dumpbin', '/imports', fpath])
72
    return output.find('QtCore4d.dll') != -1 or output.find('QtCore5d.dll') != -1
73

74 75
def is_debug_build(install_dir):
    return is_debug(os.path.join(install_dir, 'bin', 'qtcreator.exe'))
76 77 78 79 80 81 82 83 84 85 86 87 88 89 90

def op_failed(details = None):
    if details != None:
        print details
    if ignoreErrors == False:
        print("Error: operation failed!")
        sys.exit(2)
    else:
        print("Error: operation failed, but proceeding gracefully.")

def fix_rpaths_helper(chrpath_bin, install_dir, dirpath, filenames):
    # patch file
    for filename in filenames:
        fpath = os.path.join(dirpath, filename)
        relpath = os.path.relpath(install_dir+'/lib/qtcreator', dirpath)
91
        command = [chrpath_bin, '-r', '$ORIGIN/'+relpath, fpath]
92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119
        print fpath, ':', command
        try:
            subprocess.check_call(command)
        except:
            op_failed()

def check_unix_binary_exec_helper(dirpath, filename):
    """ Whether a file is really a binary executable and not a script (unix only)"""
    fpath = os.path.join(dirpath, filename)
    if os.path.exists(fpath) and os.access(fpath, os.X_OK):
        with open(fpath) as f:
            return f.read(2) != "#!"

def check_unix_library_helper(dirpath, filename):
    """ Whether a file is really a library and not a symlink (unix only)"""
    fpath = os.path.join(dirpath, filename)
    return filename.find('.so') != -1 and not os.path.islink(fpath)

def fix_rpaths(chrpath_bin, install_dir):
    print "fixing rpaths..."
    for dirpath, dirnames, filenames in os.walk(os.path.join(install_dir, 'bin')):
        #TODO remove library_helper once all libs moved out of bin/ on linux
        filenames = [filename for filename in filenames if check_unix_binary_exec_helper(dirpath, filename) or check_unix_library_helper(dirpath, filename)]
        fix_rpaths_helper(chrpath_bin, install_dir, dirpath, filenames)
    for dirpath, dirnames, filenames in os.walk(os.path.join(install_dir, 'lib')):
        filenames = [filename for filename in filenames if check_unix_library_helper(dirpath, filename)]
        fix_rpaths_helper(chrpath_bin, install_dir, dirpath, filenames)

120 121
def windows_debug_files_filter(filename):
    ignore_patterns = ['.lib', '.pdb', '.exp', '.ilk']
122 123
    for ip in ignore_patterns:
        if filename.endswith(ip):
124 125
            return True
    return False
126 127 128 129 130

def copy_ignore_patterns_helper(dir, filenames):
    if not sys.platform.startswith('win'):
        return filenames

131 132
    if debug_build:
        wrong_dlls = filter(lambda filename: filename.endswith('.dll') and not is_debug(os.path.join(dir, filename)), filenames)
133
    else:
134
        wrong_dlls = filter(lambda filename: filename.endswith('.dll') and is_debug(os.path.join(dir, filename)), filenames)
135

136
    filenames = wrong_dlls + filter(windows_debug_files_filter, filenames)
137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152
    return filenames

def copy_qt_libs(install_dir, qt_libs_dir, qt_plugin_dir, qt_import_dir, plugins, imports):
    print "copying Qt libraries..."

    if sys.platform.startswith('win'):
        libraries = glob(os.path.join(qt_libs_dir, '*.dll'))
    else:
        libraries = glob(os.path.join(qt_libs_dir, '*.so.*'))

    if sys.platform.startswith('win'):
        dest = os.path.join(install_dir, 'bin')
    else:
        dest = os.path.join(install_dir, 'lib', 'qtcreator')

    if sys.platform.startswith('win'):
153 154
        if debug_build:
            libraries = filter(lambda library: is_debug(library), libraries)
155
        else:
156
            libraries = filter(lambda library: not is_debug(library), libraries)
157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177

    for library in libraries:
        print library, '->', dest
        if os.path.islink(library):
            linkto = os.readlink(library)
            try:
                os.symlink(linkto, os.path.join(dest, os.path.basename(library)))
            except:
                op_failed("Link already exists!")
        else:
            shutil.copy(library, dest)

    copy_ignore_func = None
    if sys.platform.startswith('win'):
        copy_ignore_func = copy_ignore_patterns_helper

    print "Copying plugins:", plugins
    for plugin in plugins:
        target = os.path.join(install_dir, 'bin', plugin)
        if (os.path.exists(target)):
            shutil.rmtree(target)
178 179 180
        pluginPath = os.path.join(qt_plugin_dir, plugin)
        if (os.path.exists(pluginPath)):
            shutil.copytree(pluginPath, target, ignore=copy_ignore_func, symlinks=True)
181

182
    print "Copying imports:", imports
183 184 185 186 187 188
    for qtimport in imports:
        target = os.path.join(install_dir, 'bin', qtimport)
        if (os.path.exists(target)):
            shutil.rmtree(target)
        shutil.copytree(os.path.join(qt_import_dir, qtimport), target, ignore=copy_ignore_func, symlinks=True)

Kai Koehne's avatar
Kai Koehne committed
189 190 191 192 193 194 195
def add_qt_conf(install_dir):
    print "Creating qt.conf:"
    f = open(install_dir + '/bin/qt.conf', 'w')
    f.write('[Paths]\n')
    f.write('Libraries=../lib/qtcreator\n')
    f.close()

196
def copy_translations(install_dir, qt_tr_dir, tr_catalogs):
197
    langs = []
198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235
    tr_dir = os.path.join(install_dir, 'share', 'qtcreator', 'translations')
    p = re.compile(r'_(.*).qm')
    for dirpath, dirnames, filenames in os.walk(tr_dir):
        for filename in filenames:
            if filename.endswith('.qm') and string.find(filename, 'qtcreator_') != -1:
                lang = p.findall(filename)
                if lang != '':
                    langs += lang

    print "copying translations..."
    for lang in langs:
        for catalog in tr_catalogs:
            copy_file = "%s_%s.qm" % (catalog, lang)
            copy_src = os.path.join(qt_tr_dir, copy_file)
            copy_dst = os.path.join(tr_dir, copy_file)
            print copy_src, '->', copy_dst
            shutil.copy(copy_src, copy_dst)

def readQmakeVar(qmake_bin, var):
    pipe = os.popen(' '.join([qmake_bin, '-query', var]))
    return pipe.read().rstrip('\n')

def main():
    try:
        opts, args = getopt.gnu_getopt(sys.argv[1:], 'hi', ['help', 'ignore-errors'])
    except:
        usage()
        sys.exit(2)
    for o, a in opts:
        if o in ('-h', '--help'):
            usage()
            sys.exit(0)
        if o in ('-i', '--ignore-errors'):
            global ignoreErrors
            ignoreErrors = True
            print "Note: Ignoring all errors"

    if len(args) < 1:
236 237
        usage()
        sys.exit(2)
238

239
    install_dir = args[0]
240 241 242 243 244 245 246

    qmake_bin = 'qmake'
    if len(args) > 1:
        qmake_bin = args[1]
    qmake_bin = which(qmake_bin)

    if qmake_bin == None:
247 248
        print "Cannot find required binary 'qmake'."
        sys.exit(2)
249 250 251 252 253 254 255 256

    if not sys.platform.startswith('win'):
        chrpath_bin = which('chrpath')
        if chrpath_bin == None:
            print "Cannot find required binary 'chrpath'."
            sys.exit(2)

    QT_INSTALL_LIBS = readQmakeVar(qmake_bin, 'QT_INSTALL_LIBS')
257
    QT_INSTALL_BINS = readQmakeVar(qmake_bin, 'QT_INSTALL_BINS')
258 259 260 261
    QT_INSTALL_PLUGINS = readQmakeVar(qmake_bin, 'QT_INSTALL_PLUGINS')
    QT_INSTALL_IMPORTS = readQmakeVar(qmake_bin, 'QT_INSTALL_IMPORTS')
    QT_INSTALL_TRANSLATIONS = readQmakeVar(qmake_bin, 'QT_INSTALL_TRANSLATIONS')

262
    plugins = ['accessible', 'designer', 'iconengines', 'imageformats', 'platforms', 'sqldrivers']
263 264 265 266
    imports = ['Qt', 'QtWebKit']
    tr_catalogs = ['assistant', 'designer', 'qt', 'qt_help']

    if sys.platform.startswith('win'):
267 268
        global debug_build
        debug_build = is_debug_build(install_dir)
269

270 271 272 273
    if sys.platform.startswith('win'):
      copy_qt_libs(install_dir, QT_INSTALL_BINS, QT_INSTALL_PLUGINS, QT_INSTALL_IMPORTS, plugins, imports)
    else:
      copy_qt_libs(install_dir, QT_INSTALL_LIBS, QT_INSTALL_PLUGINS, QT_INSTALL_IMPORTS, plugins, imports)
274 275 276
    copy_translations(install_dir, QT_INSTALL_TRANSLATIONS, tr_catalogs)

    if not sys.platform.startswith('win'):
277
        fix_rpaths(chrpath_bin, install_dir)
Kai Koehne's avatar
Kai Koehne committed
278
    add_qt_conf(install_dir)
279 280 281 282 283 284 285

if __name__ == "__main__":
    if sys.platform == 'darwin':
        print "Mac OS is not supported by this script, please use macqtdeploy!"
        sys.exit(2)
    else:
        main()