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 os
41import sys
42import re
43import subprocess
44
45
46def _effective_qmake_command(qmake, qt_version):
47    """Check whether qmake path is a link to qtchooser and append the
48       desired Qt version in that case"""
49    result = [qmake]
50    # Looking whether qmake path is a link to qtchooser and whether the link
51    # exists
52    if os.path.islink(qmake) and os.path.lexists(qmake):
53        if not qt_version:
54            print('--qt must be specified when using qtchooser.')
55            sys.exit(-1)
56        # Set -qt=X here.
57        if "qtchooser" in os.readlink(qmake):
58            result.append("-qt={}".format(qt_version))
59    return result
60
61
62class QtInfo(object):
63    class __QtInfo:  # Python singleton
64        def __init__(self):
65            self._qmake_command = None
66            # Dict to cache qmake values.
67            self._query_dict = {}
68            # Dict to cache mkspecs variables.
69            self._mkspecs_dict = {}
70
71        def setup(self, qmake, qt_version):
72            self._qmake_command = _effective_qmake_command(qmake, qt_version)
73
74        def get_qmake_command(self):
75            qmake_command_string = self._qmake_command[0]
76            for entry in self._qmake_command[1:]:
77                qmake_command_string += " {}".format(entry)
78            return qmake_command_string
79
80        def get_version(self):
81            return self.get_property("QT_VERSION")
82
83        def get_bins_path(self):
84            return self.get_property("QT_INSTALL_BINS")
85
86        def get_libs_path(self):
87            return self.get_property("QT_INSTALL_LIBS")
88
89        def get_libs_execs_path(self):
90            return self.get_property("QT_INSTALL_LIBEXECS")
91
92        def get_plugins_path(self):
93            return self.get_property("QT_INSTALL_PLUGINS")
94
95        def get_prefix_path(self):
96            return self.get_property("QT_INSTALL_PREFIX")
97
98        def get_imports_path(self):
99            return self.get_property("QT_INSTALL_IMPORTS")
100
101        def get_translations_path(self):
102            return self.get_property("QT_INSTALL_TRANSLATIONS")
103
104        def get_headers_path(self):
105            return self.get_property("QT_INSTALL_HEADERS")
106
107        def get_docs_path(self):
108            return self.get_property("QT_INSTALL_DOCS")
109
110        def get_qml_path(self):
111            return self.get_property("QT_INSTALL_QML")
112
113        def get_macos_deployment_target(self):
114            """ Return value is a macOS version or None. """
115            return self.get_property("QMAKE_MACOSX_DEPLOYMENT_TARGET")
116
117        def get_build_type(self):
118            """
119            Return value is either debug, release, debug_release, or None.
120            """
121            return self.get_property("BUILD_TYPE")
122
123        def get_src_dir(self):
124            """ Return path to Qt src dir or None.. """
125            return self.get_property("QT_INSTALL_PREFIX/src")
126
127        def get_property(self, prop_name):
128            if not self._query_dict:
129                self._get_query_properties()
130                self._get_other_properties()
131            if prop_name not in self._query_dict:
132                return None
133            return self._query_dict[prop_name]
134
135        def get_mkspecs_variables(self):
136            return self._mkspecs_dict
137
138        def _get_qmake_output(self, args_list=[]):
139            assert(self._qmake_command)
140            cmd = self._qmake_command + args_list
141            proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=False)
142            output = proc.communicate()[0]
143            proc.wait()
144            if proc.returncode != 0:
145                return ""
146            if sys.version_info >= (3,):
147                output = str(output, 'ascii').strip()
148            else:
149                output = output.strip()
150            return output
151
152        def _parse_query_properties(self, process_output):
153            props = {}
154            if not process_output:
155                return props
156            lines = [s.strip() for s in process_output.splitlines()]
157            for line in lines:
158                if line and ':' in line:
159                    key, value = line.split(':', 1)
160                    props[key] = value
161            return props
162
163        def _get_query_properties(self):
164            output = self._get_qmake_output(['-query'])
165            self._query_dict = self._parse_query_properties(output)
166
167        def _parse_qt_build_type(self):
168            key = 'QT_CONFIG'
169            if key not in self._mkspecs_dict:
170                return None
171
172            qt_config = self._mkspecs_dict[key]
173            if 'debug_and_release' in qt_config:
174                return 'debug_and_release'
175
176            split = qt_config.split(' ')
177            if 'release' in split and 'debug' in split:
178                return 'debug_and_release'
179
180            if 'release' in split:
181                return 'release'
182
183            if 'debug' in split:
184                return 'debug'
185
186            return None
187
188        def _get_other_properties(self):
189            # Get the src property separately, because it is not returned by
190            # qmake unless explicitly specified.
191            key = 'QT_INSTALL_PREFIX/src'
192            result = self._get_qmake_output(['-query', key])
193            self._query_dict[key] = result
194
195            # Get mkspecs variables and cache them.
196            self._get_qmake_mkspecs_variables()
197
198            # Get macOS minimum deployment target.
199            key = 'QMAKE_MACOSX_DEPLOYMENT_TARGET'
200            if key in self._mkspecs_dict:
201                self._query_dict[key] = self._mkspecs_dict[key]
202
203            # Figure out how Qt was built:
204            #   debug mode, release mode, or both.
205            build_type = self._parse_qt_build_type()
206            if build_type:
207                self._query_dict['BUILD_TYPE'] = build_type
208
209        def _get_qmake_mkspecs_variables(self):
210            # Create empty temporary qmake project file.
211            temp_file_name = 'qmake_fake_empty_project.txt'
212            open(temp_file_name, 'a').close()
213
214            # Query qmake for all of its mkspecs variables.
215            qmake_output = self._get_qmake_output(['-E', temp_file_name])
216            lines = [s.strip() for s in qmake_output.splitlines()]
217            pattern = re.compile(r"^(.+?)=(.+?)$")
218            for line in lines:
219                found = pattern.search(line)
220                if found:
221                    key = found.group(1).strip()
222                    value = found.group(2).strip()
223                    self._mkspecs_dict[key] = value
224
225            # We need to clean up after qmake, which always creates a
226            # .qmake.stash file after a -E invocation.
227            qmake_stash_file = os.path.join(os.getcwd(), ".qmake.stash")
228            if os.path.exists(qmake_stash_file):
229                os.remove(qmake_stash_file)
230
231            # Also clean up the temporary empty project file.
232            if os.path.exists(temp_file_name):
233                os.remove(temp_file_name)
234
235        version = property(get_version)
236        bins_dir = property(get_bins_path)
237        libs_dir = property(get_libs_path)
238        lib_execs_dir = property(get_libs_execs_path)
239        plugins_dir = property(get_plugins_path)
240        prefix_dir = property(get_prefix_path)
241        qmake_command = property(get_qmake_command)
242        imports_dir = property(get_imports_path)
243        translations_dir = property(get_translations_path)
244        headers_dir = property(get_headers_path)
245        docs_dir = property(get_docs_path)
246        qml_dir = property(get_qml_path)
247        macos_min_deployment_target = property(get_macos_deployment_target)
248        build_type = property(get_build_type)
249        src_dir = property(get_src_dir)
250
251    _instance = None  # singleton helpers
252
253    def __new__(cls):  # __new__ always a classmethod
254        if not QtInfo._instance:
255            QtInfo._instance = QtInfo.__QtInfo()
256        return QtInfo._instance
257
258    def __getattr__(self, name):
259        return getattr(self._instance, name)
260
261    def __setattr__(self, name):
262        return setattr(self._instance, name)
263