1#############################################################################
2##
3## Copyright (C) 2017 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
40from __future__ import print_function
41
42"""
43testing/buildlog.py
44
45A BuildLog holds the tests of a build.
46
47BuildLog.classifiers finds the set of classifier strings.
48"""
49
50import os
51import sys
52import shutil
53from collections import namedtuple
54from textwrap import dedent
55
56from .helper import script_dir
57
58LogEntry = namedtuple("LogEntry", ["log_dir", "build_dir", "build_classifiers"])
59is_ci = os.environ.get("QTEST_ENVIRONMENT", "") == "ci"
60
61
62class BuildLog(object):
63    """
64    This class is a convenience wrapper around a list of log entries.
65
66    The list of entries is sorted by date and checked for consistency.
67    For simplicity and readability, the log entries are named tuples.
68
69    """
70    def __init__(self):
71        history_dir = os.path.join(script_dir, 'build_history')
72        build_history = []
73        for timestamp in os.listdir(history_dir):
74            log_dir = os.path.join(history_dir, timestamp)
75            if not os.path.isdir(log_dir):
76                continue
77            fpath = os.path.join(log_dir, 'build_dir.txt')
78            if not os.path.exists(fpath):
79                continue
80            with open(fpath) as f:
81                f_contents = f.read().strip()
82                f_contents_split = f_contents.splitlines()
83                try:
84                    if len(f_contents_split) == 2:
85                        build_dir = f_contents_split[0]
86                        build_classifiers = f_contents_split[1]
87                    else:
88                        build_dir = f_contents_split[0]
89                        build_classifiers = ""
90                except IndexError:
91                    print(dedent("""
92                        Error: There was an issue finding the build dir and its
93                        characteristics, in the following considered file: '{}'
94                    """.format(fpath)))
95                    sys.exit(1)
96
97                if not os.path.exists(build_dir):
98                    rel_dir, low_part = os.path.split(build_dir)
99                    rel_dir, two_part = os.path.split(rel_dir)
100                    if two_part.startswith("pyside") and two_part.endswith("build"):
101                        build_dir = os.path.abspath(os.path.join(two_part, low_part))
102                        if os.path.exists(build_dir):
103                            print("Note: build_dir was probably moved.")
104                        else:
105                            print("Warning: missing build dir %s" % build_dir)
106                            continue
107            entry = LogEntry(log_dir, build_dir, build_classifiers)
108            build_history.append(entry)
109        # we take the latest build for now.
110        build_history.sort()
111        self.history = build_history
112        self._buildno = None
113        if not is_ci:
114            # there seems to be a timing problem in RHel 7.6, so we better don't touch it
115            self.prune_old_entries(history_dir)
116
117    def prune_old_entries(self, history_dir):
118        lst = []
119        for timestamp in os.listdir(history_dir):
120            log_dir = os.path.join(history_dir, timestamp)
121            if not os.path.isdir(log_dir):
122                continue
123            lst.append(log_dir)
124        if lst:
125            def warn_problem(func, path, exc_info):
126                cls, ins, tb = exc_info
127                print("rmtree({}) warning: problem with {}:\n   {}: {}".format(
128                    func.__name__, path,
129                    cls.__name__, ins.args))
130
131            lst.sort()
132            log_dir = lst[-1]
133            fname = os.path.basename(log_dir)
134            ref_date_str = fname[:10]
135            for log_dir in lst:
136                fname = os.path.basename(log_dir)
137                date_str = fname[:10]
138                if date_str != ref_date_str:
139                    shutil.rmtree(log_dir, onerror=warn_problem)
140
141    def set_buildno(self, buildno):
142        self.history[buildno] # test
143        self._buildno = buildno
144
145    @property
146    def selected(self):
147        if self._buildno is None:
148            return None
149        if self.history is None:
150            return None
151        return self.history[self._buildno]
152
153    @property
154    def classifiers(self):
155        if not self.selected:
156            raise ValueError('+++ No build with the configuration found!')
157        # Python2 legacy: Correct 'linux2' to 'linux', recommended way.
158        platform = 'linux' if sys.platform.startswith('linux') else sys.platform
159        res = [platform]
160        if self.selected.build_classifiers:
161            # Use classifier string encoded into build_dir.txt file.
162            res.extend(self.selected.build_classifiers.split('-'))
163        else:
164            # the rest must be guessed from the given filename
165            path = self.selected.build_dir
166            base = os.path.basename(path)
167            res.extend(base.split('-'))
168        # add all the python and qt subkeys
169        for entry in res:
170            parts = entry.split(".")
171            for idx in range(len(parts)):
172                key = ".".join(parts[:idx])
173                if key not in res:
174                    res.append(key)
175        return res
176
177builds = BuildLog()
178