1#############################################################################
2##
3## Copyright (C) 2018 The Qt Company Ltd.
4## Contact: https://www.qt.io/licensing/
5##
6## This file is part of Qt for Python.
7##
8## $QT_BEGIN_LICENSE:LGPL$
9## Commercial License Usage
10## Licensees holding valid commercial Qt licenses may use this file in
11## accordance with the commercial license agreement provided with the
12## Software or, alternatively, in accordance with the terms contained in
13## a written agreement between you and The Qt Company. For licensing terms
14## and conditions see https://www.qt.io/terms-conditions. For further
15## information use the contact form at https://www.qt.io/contact-us.
16##
17## GNU Lesser General Public License Usage
18## Alternatively, this file may be used under the terms of the GNU Lesser
19## General Public License version 3 as published by the Free Software
20## Foundation and appearing in the file LICENSE.LGPL3 included in the
21## packaging of this file. Please review the following information to
22## ensure the GNU Lesser General Public License version 3 requirements
23## will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24##
25## GNU General Public License Usage
26## Alternatively, this file may be used under the terms of the GNU
27## General Public License version 2.0 or (at your option) the GNU General
28## Public license version 3 or any later version approved by the KDE Free
29## Qt Foundation. The licenses are as published by the Free Software
30## Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31## included in the packaging of this file. Please review the following
32## information to ensure the GNU General Public License requirements will
33## be met: https://www.gnu.org/licenses/gpl-2.0.html and
34## https://www.gnu.org/licenses/gpl-3.0.html.
35##
36## $QT_END_LICENSE$
37##
38#############################################################################
39
40import functools
41import os
42import sys
43import fnmatch
44
45from ..config import config
46from ..options import OPTION
47from ..utils import copydir, copyfile, makefile
48from ..utils import regenerate_qt_resources, filter_match
49from ..utils import download_and_extract_7z
50
51
52def prepare_packages_win32(self, vars):
53    # For now, debug symbols will not be shipped into the package.
54    copy_pdbs = False
55    pdbs = []
56    if (self.debug or self.build_type == 'RelWithDebInfo') and copy_pdbs:
57        pdbs = ['*.pdb']
58
59    # <install>/lib/site-packages/{st_package_name}/* ->
60    # <setup>/{st_package_name}
61    # This copies the module .pyd files and various .py files
62    # (__init__, config, git version, etc.)
63    copydir(
64        "{site_packages_dir}/{st_package_name}",
65        "{st_build_dir}/{st_package_name}",
66        vars=vars)
67
68    if config.is_internal_shiboken_module_build():
69        # <build>/shiboken2/doc/html/* ->
70        #   <setup>/{st_package_name}/docs/shiboken2
71        copydir(
72            "{build_dir}/shiboken2/doc/html",
73            "{st_build_dir}/{st_package_name}/docs/shiboken2",
74            force=False, vars=vars)
75
76        # <install>/bin/*.dll -> {st_package_name}/
77        copydir(
78            "{install_dir}/bin/",
79            "{st_build_dir}/{st_package_name}",
80            filter=["shiboken*.dll"],
81            recursive=False, vars=vars)
82
83        # <install>/lib/*.lib -> {st_package_name}/
84        copydir(
85            "{install_dir}/lib/",
86            "{st_build_dir}/{st_package_name}",
87            filter=["shiboken*.lib"],
88            recursive=False, vars=vars)
89
90        # @TODO: Fix this .pdb file not to overwrite release
91        # {shibokengenerator}.pdb file.
92        # Task-number: PYSIDE-615
93        copydir(
94            "{build_dir}/shiboken2/shibokenmodule",
95            "{st_build_dir}/{st_package_name}",
96            filter=pdbs,
97            recursive=False, vars=vars)
98
99        # pdb files for libshiboken and libpyside
100        copydir(
101            "{build_dir}/shiboken2/libshiboken",
102            "{st_build_dir}/{st_package_name}",
103            filter=pdbs,
104            recursive=False, vars=vars)
105
106    if config.is_internal_shiboken_generator_build():
107        # <install>/bin/*.dll -> {st_package_name}/
108        copydir(
109            "{install_dir}/bin/",
110            "{st_build_dir}/{st_package_name}",
111            filter=["shiboken*.exe"],
112            recursive=False, vars=vars)
113
114        # Used to create scripts directory.
115        makefile(
116            "{st_build_dir}/{st_package_name}/scripts/shiboken_tool.py",
117            vars=vars)
118
119        # For setting up setuptools entry points.
120        copyfile(
121            "{install_dir}/bin/shiboken_tool.py",
122            "{st_build_dir}/{st_package_name}/scripts/shiboken_tool.py",
123            force=False, vars=vars)
124
125        # @TODO: Fix this .pdb file not to overwrite release
126        # {shibokenmodule}.pdb file.
127        # Task-number: PYSIDE-615
128        copydir(
129            "{build_dir}/shiboken2/generator",
130            "{st_build_dir}/{st_package_name}",
131            filter=pdbs,
132            recursive=False, vars=vars)
133
134    if config.is_internal_shiboken_generator_build() or config.is_internal_pyside_build():
135        # <install>/include/* -> <setup>/{st_package_name}/include
136        copydir(
137            "{install_dir}/include/{cmake_package_name}",
138            "{st_build_dir}/{st_package_name}/include",
139            vars=vars)
140
141    if config.is_internal_pyside_build():
142        # <build>/pyside2/{st_package_name}/*.pdb ->
143        # <setup>/{st_package_name}
144        copydir(
145            "{build_dir}/pyside2/{st_package_name}",
146            "{st_build_dir}/{st_package_name}",
147            filter=pdbs,
148            recursive=False, vars=vars)
149
150        makefile(
151            "{st_build_dir}/{st_package_name}/scripts/__init__.py",
152            vars=vars)
153
154        # For setting up setuptools entry points
155        copyfile(
156            "{install_dir}/bin/pyside_tool.py",
157            "{st_build_dir}/{st_package_name}/scripts/pyside_tool.py",
158            force=False, vars=vars)
159
160        # <install>/bin/*.exe,*.dll -> {st_package_name}/
161        copydir(
162            "{install_dir}/bin/",
163            "{st_build_dir}/{st_package_name}",
164            filter=["pyside*.exe", "pyside*.dll", "uic.exe", "rcc.exe", "designer.exe"],
165            recursive=False, vars=vars)
166
167        # <install>/lib/*.lib -> {st_package_name}/
168        copydir(
169            "{install_dir}/lib/",
170            "{st_build_dir}/{st_package_name}",
171            filter=["pyside*.lib"],
172            recursive=False, vars=vars)
173
174        # <install>/share/{st_package_name}/typesystems/* ->
175        #   <setup>/{st_package_name}/typesystems
176        copydir(
177            "{install_dir}/share/{st_package_name}/typesystems",
178            "{st_build_dir}/{st_package_name}/typesystems",
179            vars=vars)
180
181        # <install>/share/{st_package_name}/glue/* ->
182        #   <setup>/{st_package_name}/glue
183        copydir(
184            "{install_dir}/share/{st_package_name}/glue",
185            "{st_build_dir}/{st_package_name}/glue",
186            vars=vars)
187
188        # <source>/pyside2/{st_package_name}/support/* ->
189        #   <setup>/{st_package_name}/support/*
190        copydir(
191            "{build_dir}/pyside2/{st_package_name}/support",
192            "{st_build_dir}/{st_package_name}/support",
193            vars=vars)
194
195        # <source>/pyside2/{st_package_name}/*.pyi ->
196        #   <setup>/{st_package_name}/*.pyi
197        copydir(
198            "{build_dir}/pyside2/{st_package_name}",
199            "{st_build_dir}/{st_package_name}",
200            filter=["*.pyi", "py.typed"],
201            vars=vars)
202
203        copydir(
204            "{build_dir}/pyside2/libpyside",
205            "{st_build_dir}/{st_package_name}",
206            filter=pdbs,
207            recursive=False, vars=vars)
208
209        if not OPTION["NOEXAMPLES"]:
210            def pycache_dir_filter(dir_name, parent_full_path, dir_full_path):
211                if fnmatch.fnmatch(dir_name, "__pycache__"):
212                    return False
213                return True
214            # examples/* -> <setup>/{st_package_name}/examples
215            copydir(os.path.join(self.script_dir, "examples"),
216                    "{st_build_dir}/{st_package_name}/examples",
217                    force=False, vars=vars, dir_filter_function=pycache_dir_filter)
218            # Re-generate examples Qt resource files for Python 3
219            # compatibility
220            if sys.version_info[0] == 3:
221                examples_path = "{st_build_dir}/{st_package_name}/examples".format(
222                    **vars)
223                pyside_rcc_path = "{install_dir}/bin/rcc.exe".format(
224                    **vars)
225                pyside_rcc_options = ['-g', 'python']
226                regenerate_qt_resources(examples_path, pyside_rcc_path, pyside_rcc_options)
227
228        if vars['ssl_libs_dir']:
229            # <ssl_libs>/* -> <setup>/{st_package_name}/openssl
230            copydir("{ssl_libs_dir}", "{st_build_dir}/{st_package_name}/openssl",
231                    filter=[
232                        "libeay32.dll",
233                        "ssleay32.dll"],
234                    force=False, vars=vars)
235
236    if config.is_internal_shiboken_module_build():
237        # The C++ std library dlls need to be packaged with the
238        # shiboken module, because libshiboken uses C++ code.
239        copy_msvc_redist_files(vars, "{build_dir}/msvc_redist".format(**vars))
240
241    if config.is_internal_pyside_build() or config.is_internal_shiboken_generator_build():
242        copy_qt_artifacts(self, copy_pdbs, vars)
243        copy_msvc_redist_files(vars, "{build_dir}/msvc_redist".format(**vars))
244
245
246def copy_msvc_redist_files(vars, redist_target_path):
247    # MSVC redistributable file list.
248    msvc_redist = [
249        "concrt140.dll",
250        "msvcp140.dll",
251        "ucrtbase.dll",
252        "vcamp140.dll",
253        "vccorlib140.dll",
254        "vcomp140.dll",
255        "vcruntime140.dll",
256        "vcruntime140_1.dll",
257        "msvcp140_1.dll",
258        "msvcp140_2.dll",
259        "msvcp140_codecvt_ids.dll"
260    ]
261
262    # Make a directory where the files should be extracted.
263    if not os.path.exists(redist_target_path):
264        os.makedirs(redist_target_path)
265
266    # Extract Qt dependency dlls when building on Qt CI.
267    in_coin = os.environ.get('COIN_LAUNCH_PARAMETERS', None)
268    if in_coin is not None:
269        redist_url = "http://download.qt.io/development_releases/prebuilt/vcredist/"
270        zip_file = "pyside_qt_deps_64_2019.7z"
271        if "{target_arch}".format(**vars) == "32":
272            zip_file = "pyside_qt_deps_32_2019.7z"
273        download_and_extract_7z(redist_url + zip_file, redist_target_path)
274    else:
275        print("Qt dependency DLLs (MSVC redist) will not be downloaded and extracted.")
276
277    copydir(redist_target_path,
278            "{st_build_dir}/{st_package_name}",
279            filter=msvc_redist, recursive=False, vars=vars)
280
281
282def copy_qt_artifacts(self, copy_pdbs, vars):
283    built_modules = self.get_built_pyside_config(vars)['built_modules']
284
285    constrain_modules = None
286    copy_plugins = True
287    copy_qml = True
288    copy_translations = True
289    copy_qt_conf = True
290    copy_qt_permanent_artifacts = True
291    copy_msvc_redist = False
292    copy_clang = False
293
294    if config.is_internal_shiboken_generator_build():
295        constrain_modules = ["Core", "Network", "Xml", "XmlPatterns"]
296        copy_plugins = False
297        copy_qml = False
298        copy_translations = False
299        copy_qt_conf = False
300        copy_qt_permanent_artifacts = False
301        copy_msvc_redist = True
302        copy_clang = True
303
304    # <qt>/bin/*.dll and Qt *.exe -> <setup>/{st_package_name}
305    qt_artifacts_permanent = [
306        "opengl*.dll",
307        "d3d*.dll",
308        "designer.exe",
309        "linguist.exe",
310        "lrelease.exe",
311        "lupdate.exe",
312        "lconvert.exe",
313        "qtdiag.exe"
314    ]
315
316    # Choose which EGL library variants to copy.
317    qt_artifacts_egl = [
318        "libEGL{}.dll",
319        "libGLESv2{}.dll"
320    ]
321    if self.qtinfo.build_type != 'debug_and_release':
322        egl_suffix = '*'
323    elif self.debug:
324        egl_suffix = 'd'
325    else:
326        egl_suffix = ''
327    qt_artifacts_egl = [a.format(egl_suffix) for a in qt_artifacts_egl]
328
329    artifacts = []
330    if copy_qt_permanent_artifacts:
331        artifacts += qt_artifacts_permanent
332        artifacts += qt_artifacts_egl
333
334    if copy_msvc_redist:
335        # The target path has to be qt_bin_dir at the moment,
336        # because the extracted archive also contains the opengl32sw
337        # and the d3dcompiler dlls, which are copied not by this
338        # function, but by the copydir below.
339        copy_msvc_redist_files(vars, "{qt_bin_dir}".format(**vars))
340
341    if artifacts:
342        copydir("{qt_bin_dir}",
343                "{st_build_dir}/{st_package_name}",
344                filter=artifacts, recursive=False, vars=vars)
345
346    # <qt>/bin/*.dll and Qt *.pdbs -> <setup>/{st_package_name} part two
347    # File filter to copy only debug or only release files.
348    if constrain_modules:
349        qt_dll_patterns = ["Qt5" + x + "{}.dll" for x in constrain_modules]
350        if copy_pdbs:
351            qt_dll_patterns += ["Qt5" + x + "{}.pdb" for x in constrain_modules]
352    else:
353        qt_dll_patterns = ["Qt5*{}.dll", "lib*{}.dll"]
354        if copy_pdbs:
355            qt_dll_patterns += ["Qt5*{}.pdb", "lib*{}.pdb"]
356
357    def qt_build_config_filter(patterns, file_name, file_full_path):
358        release = [a.format('') for a in patterns]
359        debug = [a.format('d') for a in patterns]
360
361        # If qt is not a debug_and_release build, that means there
362        # is only one set of shared libraries, so we can just copy
363        # them.
364        if self.qtinfo.build_type != 'debug_and_release':
365            if filter_match(file_name, release):
366                return True
367            return False
368
369        # In debug_and_release case, choosing which files to copy
370        # is more difficult. We want to copy only the files that
371        # match the PySide2 build type. So if PySide2 is built in
372        # debug mode, we want to copy only Qt debug libraries
373        # (ending with "d.dll"). Or vice versa. The problem is that
374        # some libraries have "d" as the last character of the
375        # actual library name (for example Qt5Gamepad.dll and
376        # Qt5Gamepadd.dll). So we can't just match a pattern ending
377        # in "d". Instead we check if there exists a file with the
378        # same name plus an additional "d" at the end, and using
379        # that information we can judge if the currently processed
380        # file is a debug or release file.
381
382        # e.g. ["Qt5Cored", ".dll"]
383        file_split = os.path.splitext(file_name)
384        file_base_name = file_split[0]
385        file_ext = file_split[1]
386        # e.g. "/home/work/qt/qtbase/bin"
387        file_path_dir_name = os.path.dirname(file_full_path)
388        # e.g. "Qt5Coredd"
389        maybe_debug_name = "{}d".format(file_base_name)
390        if self.debug:
391            filter = debug
392
393            def predicate(path):
394                return not os.path.exists(path)
395        else:
396            filter = release
397
398            def predicate(path):
399                return os.path.exists(path)
400        # e.g. "/home/work/qt/qtbase/bin/Qt5Coredd.dll"
401        other_config_path = os.path.join(file_path_dir_name, maybe_debug_name + file_ext)
402
403        if (filter_match(file_name, filter) and predicate(other_config_path)):
404            return True
405        return False
406
407    qt_dll_filter = functools.partial(qt_build_config_filter,
408                                      qt_dll_patterns)
409    copydir("{qt_bin_dir}",
410            "{st_build_dir}/{st_package_name}",
411            file_filter_function=qt_dll_filter,
412            recursive=False, vars=vars)
413
414    if copy_plugins:
415        # <qt>/plugins/* -> <setup>/{st_package_name}/plugins
416        plugin_dll_patterns = ["*{}.dll"]
417        pdb_pattern = "*{}.pdb"
418        if copy_pdbs:
419            plugin_dll_patterns += [pdb_pattern]
420        plugin_dll_filter = functools.partial(qt_build_config_filter, plugin_dll_patterns)
421        copydir("{qt_plugins_dir}", "{st_build_dir}/{st_package_name}/plugins",
422                file_filter_function=plugin_dll_filter,
423                vars=vars)
424
425    if copy_translations:
426        # <qt>/translations/* -> <setup>/{st_package_name}/translations
427        copydir("{qt_translations_dir}",
428                "{st_build_dir}/{st_package_name}/translations",
429                filter=["*.qm", "*.pak"],
430                force=False,
431                vars=vars)
432
433    if copy_qml:
434        # <qt>/qml/* -> <setup>/{st_package_name}/qml
435        qml_dll_patterns = ["*{}.dll"]
436        qml_ignore_patterns = qml_dll_patterns + [pdb_pattern]
437        qml_ignore = [a.format('') for a in qml_ignore_patterns]
438
439        # Copy all files that are not dlls and pdbs (.qml, qmldir).
440        copydir("{qt_qml_dir}", "{st_build_dir}/{st_package_name}/qml",
441                ignore=qml_ignore,
442                force=False,
443                recursive=True,
444                vars=vars)
445
446        if copy_pdbs:
447            qml_dll_patterns += [pdb_pattern]
448        qml_dll_filter = functools.partial(qt_build_config_filter, qml_dll_patterns)
449
450        # Copy all dlls (and possibly pdbs).
451        copydir("{qt_qml_dir}", "{st_build_dir}/{st_package_name}/qml",
452                file_filter_function=qml_dll_filter,
453                force=False,
454                recursive=True,
455                vars=vars)
456
457    if self.is_webengine_built(built_modules):
458        copydir("{qt_prefix_dir}/resources",
459                "{st_build_dir}/{st_package_name}/resources",
460                filter=None,
461                recursive=False,
462                vars=vars)
463
464        filter = 'QtWebEngineProcess{}.exe'.format(
465            'd' if self.debug else '')
466        copydir("{qt_bin_dir}",
467                "{st_build_dir}/{st_package_name}",
468                filter=[filter],
469                recursive=False, vars=vars)
470
471    if copy_qt_conf:
472        # Copy the qt.conf file to prefix dir.
473        copyfile("{build_dir}/pyside2/{st_package_name}/qt.conf",
474                 "{st_build_dir}/{st_package_name}",
475                 vars=vars)
476
477    if copy_clang:
478        self.prepare_standalone_clang(is_win=True)
479