summaryrefslogtreecommitdiff
path: root/CC/auto/runner
blob: 2da44af8b1407858e9084eb4f2b9b7452901d2d0 (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
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
#!/usr/bin/env python3

# Copyright 2022 Huawei Cloud Computing Technology Co., Ltd.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import json
import re
from sys import argv
from typing import Union


def get_tokens(line: str, magic_string: str) -> tuple[Union[re.Match[str], None], bool]:
    """Tokenize lines (strings) like the following
    #cmakedefine FOO bar
    #cmakedefine FOO @FOO@
    #cmakedefine FOO ${FOO}
    #cmakedefine01 FOO

    where "cmakedefine" is the magic_string. Let us name "FOO" as the token_key,
    and the corresponding value (i.e., bar, @FOO@, ${FOO}, the empty string) as
    the token_value. The function handles any combination of spaces around the
    magic_string, the token_key and the token_value.
    """
    x = re.search(
        r"#(.*)(" + magic_string + r"[01]*" + r")([\s]*)([a-zA-Z0-9_]+)([\s]*)(.*)",
        line,
    )
    if x:
        return x, x.groups()[1] == f"{magic_string}01"
    return None, False


def handle01(line: str, tokens: re.Match[str], defined: bool) -> str:
    groups = tokens.groups()
    return re.sub(
        tokens.group()[1:],
        groups[0]  # spaces
        + "define"
        + groups[2]  # spaces
        + groups[3]  # token_key
        + " "
        + str(1 if defined else 0),
        line,
    )


def undefine(tokens: re.Match[str]) -> str:
    groups = tokens.groups()
    return "/* #" + groups[0] + "undef" + groups[2] + groups[3] + " */"


def replace_value(tokens: re.Match[str], key: str, value: str) -> str:
    groups = tokens.groups()
    return f"#{groups[0]}define{groups[2]}{key}{groups[4]}{value}"


def compute_value(token_value: str, at_only: bool, param: dict[str, str]):
    # example of possible token_values
    #  - foo (a simple string)
    #  - @FOO@
    #  - ${FOO}
    #  - any combination of the above

    def replace_pattern_in_string(
        pattern: str, line: str, param: dict[str, str]
    ) -> str:

        def get_value_for_match(match: re.Match[str]) -> str:
            key = match.group(1)
            return param.get(key, "")

        return re.sub(pattern, get_value_for_match, line)

    token_value = replace_pattern_in_string(r"@([A-Za-z0-9_]+)@", token_value, param)
    if at_only:
        return token_value
    return replace_pattern_in_string(r"\${([A-Za-z0-9_]+)}", token_value, param)


if __name__ == "__main__":
    input_file = argv[1]
    param_file = argv[2]
    magic_string = argv[3]
    at_only = argv[4] == "true"

    with open(param_file) as f:
        param = json.loads(f.read())

    # In many cases, CMake simply defines some variables (without any associated
    # value). We handle this situation by assigning to the boolean True the empty
    # string. Note that no False value should be found, because the right way to set
    # a variable to False in the TARGETS file is to *do not mention* that variable
    # at all.
    # If a value is deliberately set to null, we will drop that key
    drop_keys: list[str] = []
    for k, v in param.items():
        if isinstance(v, bool):
            param[k] = ""
        if v == None:
            drop_keys.append(k)
    for k in drop_keys:
        del param[k]

    with open(input_file) as i:
        with open("out", "w") as o:
            for line in i.readlines():
                # drop trailing '\n'
                line = line[:-1]

                tokens, is_01 = get_tokens(line, magic_string)
                # no magic string
                if not tokens:
                    # it can be a simple comment, or a line without the magic string but with the @KEY@ or ${KEY} pattern
                    line = compute_value(line, at_only, param)
                    print(line, file=o)
                    continue

                # line contains magic_string
                groups = tokens.groups()

                token_key: str = groups[3]

                if is_01:
                    line = handle01(line, tokens, token_key in param)
                    print(line, file=o)
                    continue

                if token_key not in param:
                    line = undefine(tokens)
                    print(line, file=o)
                    continue

                # we are in one of this situations
                # cmakedefine FOO
                # cmakedefine FOO "foo"
                # cmakedefine FOO @FOO@${FOO}foo

                # i.e., the token_value can be any combination of keys (defined
                # as @key@ or ${key}) and strings

                # therefore, we need to further tokenize the token_value

                # it is convenient to first tokenize

                token_value: str = groups[5]

                value = compute_value(token_value, at_only, param)

                line = replace_value(tokens, token_key, value)

                print(line, file=o)