1############################################################################ 2# 3# Copyright (C) 2016 The Qt Company Ltd. 4# Contact: https://www.qt.io/licensing/ 5# 6# This file is part of Qt Creator. 7# 8# Commercial License Usage 9# Licensees holding valid commercial Qt licenses may use this file in 10# accordance with the commercial license agreement provided with the 11# Software or, alternatively, in accordance with the terms contained in 12# a written agreement between you and The Qt Company. For licensing terms 13# and conditions see https://www.qt.io/terms-conditions. For further 14# information use the contact form at https://www.qt.io/contact-us. 15# 16# GNU General Public License Usage 17# Alternatively, this file may be used under the terms of the GNU 18# General Public License version 3 as published by the Free Software 19# Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT 20# included in the packaging of this file. Please review the following 21# information to ensure the GNU General Public License requirements will 22# be met: https://www.gnu.org/licenses/gpl-3.0.html. 23# 24############################################################################ 25 26import os 27import locale 28import shutil 29import subprocess 30import sys 31 32encoding = locale.getdefaultlocale()[1] 33if not encoding: 34 encoding = 'UTF-8' 35 36def is_windows_platform(): 37 return sys.platform.startswith('win') 38 39def is_linux_platform(): 40 return sys.platform.startswith('linux') 41 42def is_mac_platform(): 43 return sys.platform.startswith('darwin') 44 45def to_posix_path(path): 46 if is_windows_platform(): 47 # should switch to pathlib from python3 48 return path.replace('\\', '/') 49 return path 50 51def check_print_call(command, workdir=None, env=None): 52 print('------------------------------------------') 53 print('COMMAND:') 54 print(' '.join(['"' + c.replace('"', '\\"') + '"' for c in command])) 55 print('PWD: "' + (workdir if workdir else os.getcwd()) + '"') 56 print('------------------------------------------') 57 subprocess.check_call(command, cwd=workdir, env=env) 58 59 60def get_git_SHA(path): 61 try: 62 output = subprocess.check_output(['git', 'rev-list', '-n1', 'HEAD'], cwd=path).strip() 63 decoded_output = output.decode(encoding) if encoding else output 64 return decoded_output 65 except subprocess.CalledProcessError: 66 return None 67 return None 68 69 70# get commit SHA either directly from git, or from a .tag file in the source directory 71def get_commit_SHA(path): 72 git_sha = get_git_SHA(path) 73 if not git_sha: 74 tagfile = os.path.join(path, '.tag') 75 if os.path.exists(tagfile): 76 with open(tagfile, 'r') as f: 77 git_sha = f.read().strip() 78 return git_sha 79 80# copy of shutil.copytree that does not bail out if the target directory already exists 81# and that does not create empty directories 82def copytree(src, dst, symlinks=False, ignore=None): 83 def ensure_dir(destdir, ensure): 84 if ensure and not os.path.isdir(destdir): 85 os.makedirs(destdir) 86 return False 87 88 names = os.listdir(src) 89 if ignore is not None: 90 ignored_names = ignore(src, names) 91 else: 92 ignored_names = set() 93 94 needs_ensure_dest_dir = True 95 errors = [] 96 for name in names: 97 if name in ignored_names: 98 continue 99 srcname = os.path.join(src, name) 100 dstname = os.path.join(dst, name) 101 try: 102 if symlinks and os.path.islink(srcname): 103 needs_ensure_dest_dir = ensure_dir(dst, needs_ensure_dest_dir) 104 linkto = os.readlink(srcname) 105 os.symlink(linkto, dstname) 106 elif os.path.isdir(srcname): 107 copytree(srcname, dstname, symlinks, ignore) 108 else: 109 needs_ensure_dest_dir = ensure_dir(dst, needs_ensure_dest_dir) 110 shutil.copy2(srcname, dstname) 111 # XXX What about devices, sockets etc.? 112 except (IOError, os.error) as why: 113 errors.append((srcname, dstname, str(why))) 114 # catch the Error from the recursive copytree so that we can 115 # continue with other files 116 except shutil.Error as err: 117 errors.extend(err.args[0]) 118 try: 119 if os.path.exists(dst): 120 shutil.copystat(src, dst) 121 except shutil.WindowsError: 122 # can't copy file access times on Windows 123 pass 124 except OSError as why: 125 errors.extend((src, dst, str(why))) 126 if errors: 127 raise shutil.Error(errors) 128 129def get_qt_install_info(qmake_bin): 130 output = subprocess.check_output([qmake_bin, '-query']) 131 decoded_output = output.decode(encoding) if encoding else output 132 lines = decoded_output.strip().split('\n') 133 info = {} 134 for line in lines: 135 (var, sep, value) = line.partition(':') 136 info[var.strip()] = value.strip() 137 return info 138 139def get_rpath(libfilepath, chrpath=None): 140 if chrpath is None: 141 chrpath = 'chrpath' 142 try: 143 output = subprocess.check_output([chrpath, '-l', libfilepath]).strip() 144 decoded_output = output.decode(encoding) if encoding else output 145 except subprocess.CalledProcessError: # no RPATH or RUNPATH 146 return [] 147 marker = 'RPATH=' 148 index = decoded_output.find(marker) 149 if index < 0: 150 marker = 'RUNPATH=' 151 index = decoded_output.find(marker) 152 if index < 0: 153 return [] 154 return decoded_output[index + len(marker):].split(':') 155 156def fix_rpaths(path, qt_deploy_path, qt_install_info, chrpath=None): 157 if chrpath is None: 158 chrpath = 'chrpath' 159 qt_install_prefix = qt_install_info['QT_INSTALL_PREFIX'] 160 qt_install_libs = qt_install_info['QT_INSTALL_LIBS'] 161 162 def fix_rpaths_helper(filepath): 163 rpath = get_rpath(filepath, chrpath) 164 if len(rpath) <= 0: 165 return 166 # remove previous Qt RPATH 167 new_rpath = list(filter(lambda path: not path.startswith(qt_install_prefix) and not path.startswith(qt_install_libs), 168 rpath)) 169 170 # check for Qt linking 171 lddOutput = subprocess.check_output(['ldd', filepath]) 172 lddDecodedOutput = lddOutput.decode(encoding) if encoding else lddOutput 173 if lddDecodedOutput.find('libQt5') >= 0 or lddDecodedOutput.find('libicu') >= 0: 174 # add Qt RPATH if necessary 175 relative_path = os.path.relpath(qt_deploy_path, os.path.dirname(filepath)) 176 if relative_path == '.': 177 relative_path = '' 178 else: 179 relative_path = '/' + relative_path 180 qt_rpath = '$ORIGIN' + relative_path 181 if not any((path == qt_rpath) for path in rpath): 182 new_rpath.append(qt_rpath) 183 184 # change RPATH 185 if len(new_rpath) > 0: 186 subprocess.check_call([chrpath, '-r', ':'.join(new_rpath), filepath]) 187 else: # no RPATH / RUNPATH left. delete. 188 subprocess.check_call([chrpath, '-d', filepath]) 189 190 def is_unix_executable(filepath): 191 # Whether a file is really a binary executable and not a script and not a symlink (unix only) 192 if os.path.exists(filepath) and os.access(filepath, os.X_OK) and not os.path.islink(filepath): 193 with open(filepath, 'rb') as f: 194 return f.read(2) != "#!" 195 196 def is_unix_library(filepath): 197 # Whether a file is really a library and not a symlink (unix only) 198 return os.path.basename(filepath).find('.so') != -1 and not os.path.islink(filepath) 199 200 for dirpath, dirnames, filenames in os.walk(path): 201 for filename in filenames: 202 filepath = os.path.join(dirpath, filename) 203 if is_unix_executable(filepath) or is_unix_library(filepath): 204 fix_rpaths_helper(filepath) 205 206def is_debug_file(filepath): 207 if is_mac_platform(): 208 return filepath.endswith('.dSYM') or '.dSYM/' in filepath 209 elif is_linux_platform(): 210 return filepath.endswith('.debug') 211 else: 212 return filepath.endswith('.pdb') 213 214def is_debug(path, filenames): 215 return [fn for fn in filenames if is_debug_file(os.path.join(path, fn))] 216 217def is_not_debug(path, filenames): 218 files = [fn for fn in filenames if os.path.isfile(os.path.join(path, fn))] 219 return [fn for fn in files if not is_debug_file(os.path.join(path, fn))] 220 221def codesign_call(): 222 signing_identity = os.environ.get('SIGNING_IDENTITY') 223 if not signing_identity: 224 return None 225 codesign_call = ['codesign', '-o', 'runtime', '--force', '-s', signing_identity, 226 '-v'] 227 signing_flags = os.environ.get('SIGNING_FLAGS') 228 if signing_flags: 229 codesign_call.extend(signing_flags.split()) 230 return codesign_call 231 232def os_walk(path, filter, function): 233 for r, _, fs in os.walk(path): 234 for f in fs: 235 ff = os.path.join(r, f) 236 if filter(ff): 237 function(ff) 238 239def conditional_sign_recursive(path, filter): 240 codesign = codesign_call() 241 if is_mac_platform() and codesign: 242 os_walk(path, filter, lambda fp: subprocess.check_call(codesign + [fp])) 243 244def codesign(app_path): 245 # sign all executables in Resources 246 conditional_sign_recursive(os.path.join(app_path, 'Contents', 'Resources'), 247 lambda ff: os.access(ff, os.X_OK)) 248 # sign all libraries in Imports 249 conditional_sign_recursive(os.path.join(app_path, 'Contents', 'Imports'), 250 lambda ff: ff.endswith('.dylib')) 251 codesign = codesign_call() 252 if is_mac_platform() and codesign: 253 entitlements_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), '..', 'dist', 254 'installer', 'mac', 'entitlements.plist') 255 # sign the whole bundle 256 subprocess.check_call(codesign + ['--deep', app_path, '--entitlements', entitlements_path]) 257