Read-only mirrors. Development, issues and pull requests are on GitHub.

summaryrefslogtreecommitdiffhomepage
path: root/tools/scripts/checkConvenienceHeaders.py
blob: 6a8b7bc3af7704b4b741e2bcf3ed700ab29c3f50 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
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
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
#!/usr/bin/env python3

"""
Checks that all of the "All.hpp" headers of the engine include all subheaders.

The logic is simple: given a folder, e.g. `Progmasoft/Catch3/Matchers`, the
corresponding header is `Progmasoft/Catch3/Matchers/All.hpp` and contains
* all engine headers in `Progmasoft/Catch3/Matchers`,
* all engine headers in `Progmasoft/Catch3/Matchers/{Internal, Detail}`,
* the `All.hpp` headers of its other subfolders that hold engine headers.

The top level header is `Progmasoft/Catch3/All.hpp`. Engine headers are the
ones listed in upstreamFiles.txt; Progmasoft's own headers in the same
folders are not part of the convenience headers.
"""

internal_dirs = ['Detail', 'Internal']

from scriptCommon import catchPath, upstreamFiles
import os
import re
import sys

source_root = 'src/Progmasoft/Catch3'
include_parser = re.compile(r'#include <(Progmasoft/Catch3/.+\.hpp)>')
engine_headers = {path[len('src/'):] for path in upstreamFiles() if path.endswith('.hpp')}

errors_found = False


def engine_headers_in(folder):
    """Engine headers directly in folder, as include paths."""
    prefix = folder + '/'
    return sorted(h for h in engine_headers
                  if h.startswith(prefix) and '/' not in h[len(prefix):])


def subfolders_with_engine_headers(folder):
    prefix = folder + '/'
    names = set()
    for header in engine_headers:
        if header.startswith(prefix):
            rest = header[len(prefix):]
            if '/' in rest:
                names.add(rest.split('/')[0])
    return sorted(names)


def collated_includes(folder):
    base = engine_headers_in(folder)
    for name in subfolders_with_engine_headers(folder):
        subfolder = folder + '/' + name
        if name in internal_dirs:
            base.extend(engine_headers_in(subfolder))
        else:
            base.append(subfolder + '/All.hpp')
    return sorted(base)


def includes_from_file(header):
    includes = []
    with open(os.path.join(catchPath, 'src', header), 'r', encoding='utf-8') as file:
        for line in file:
            if not line.startswith('#include'):
                continue
            match = include_parser.match(line)
            if match:
                includes.append(match.group(1))
    return includes


def get_duplicates(xs):
    seen = set()
    duplicated = []
    for x in xs:
        if x in seen:
            duplicated.append(x)
        seen.add(x)
    return duplicated


def verify_convenience_header(folder):
    """
    Checks the convenience header of one folder:
    1) The header exists
    2) Its includes are sorted
    3) It has no duplicated includes
    4) It includes every header it should
    5) It includes nothing else
    """
    global errors_found
    header_name = folder + '/All.hpp'

    # 1) Does it exist?
    if header_name not in engine_headers:
        errors_found = True
        print('Missing convenience header: {}'.format(header_name))
        return
    file_incs = includes_from_file(header_name)
    # 2) Are the includes sorted?
    if sorted(file_incs) != file_incs:
        errors_found = True
        print("'{}': Includes are not in sorted order!".format(header_name))

    # 3) Are there no duplicates?
    for duplicate in get_duplicates(file_incs):
        errors_found = True
        print("'{}': Duplicated include: '{}'".format(header_name, duplicate))

    # The convenience header does not include itself.
    target_includes = [x for x in collated_includes(folder) if x != header_name]
    # 4) Are all required headers present?
    file_incs_set = set(file_incs)
    for include in target_includes:
        if (include not in file_incs_set and
                include != 'Progmasoft/Catch3/Internal/WindowsHProxy.hpp'):
            errors_found = True
            print("'{}': missing include '{}'".format(header_name, include))

    # 5) Are there any superfluous headers?
    desired_set = set(target_includes)
    for include in file_incs:
        if include not in desired_set:
            errors_found = True
            print("'{}': superfluous include '{}'".format(header_name, include))


def walk_source_folders(folder):
    verify_convenience_header(folder)
    for name in subfolders_with_engine_headers(folder):
        if name not in internal_dirs:
            walk_source_folders(folder + '/' + name)


walk_source_folders('Progmasoft/Catch3')

# Propagate error "code" upwards
if not errors_found:
    print('Everything ok')
else:
    sys.exit(1)