1"""
2Utility script to migrate Zephyr-based projects to normative POSIX Kconfig options.
3
4This script should be used for migrating from versions of Zephyr older than v3.7.0 to Zephyr
5version v3.7.0 or later.
6
7Usage::
8
9    python $ZEPHYR_BASE/scripts/utils/migrate_posix_kconfigs.py -r root_path
10
11The utility will process c, cpp, h, hpp, rst, conf, CMakeLists.txt,
12yml, yaml and Kconfig files.
13
14
15Copyright (c) 2022 Nordic Semiconductor ASA
16Copyright (c) 2024 Tenstorrent AI ULC
17SPDX-License-Identifier: Apache-2.0
18"""
19
20import argparse
21import re
22import sys
23from pathlib import Path
24
25ZEPHYR_BASE = Path(__file__).parents[2]
26
27FILE_PATTERNS = (
28    r".+\.c", r".+\.cpp", r".+\.hpp", r".+\.h", r".+\.rst", r".+\.conf",
29    r".+\.yml", r".+\.yaml", r"CMakeLists.txt", r"Kconfig(\..+)?"
30)
31
32REPLACEMENTS = {
33    "EVENTFD_MAX": "ZVFS_EVENTFD_MAX",
34    "FNMATCH": "POSIX_C_LIB_EXT",
35    "GETENTROPY": "POSIX_C_LIB_EXT",
36    "GETOPT": "POSIX_C_LIB_EXT",
37    "MAX_PTHREAD_COUNT": "POSIX_THREAD_THREADS_MAX",
38    "MAX_PTHREAD_KEY_COUNT": "POSIX_THREAD_KEYS_MAX",
39    "MAX_TIMER_COUNT": "POSIX_TIMER_MAX",
40    "MSG_COUNT_MAX": "POSIX_MQ_OPEN_MAX",
41    "POSIX_CLOCK": "POSIX_TIMERS",
42    "POSIX_CONFSTR": "POSIX_SINGLE_PROCESS",
43    "POSIX_ENV": "POSIX_SINGLE_PROCESS",
44    "POSIX_FS": "POSIX_FILE_SYSTEM",
45    "POSIX_LIMITS_RTSIG_MAX": "POSIX_RTSIG_MAX",
46    "POSIX_MAX_FDS": "ZVFS_OPEN_MAX",
47    "POSIX_MAX_OPEN_FILES": "ZVFS_OPEN_MAX",
48    "POSIX_MQUEUE": "POSIX_MESSAGE_PASSING",
49    "POSIX_PUTMSG": "XOPEN_STREAMS",
50    "POSIX_SIGNAL": "POSIX_SIGNALS",
51    "POSIX_SYSCONF": "POSIX_SINGLE_PROCESS",
52    "POSIX_SYSLOG": "XSI_SYSTEM_LOGGING",
53    "POSIX_UNAME": "POSIX_SINGLE_PROCESS",
54    "PTHREAD": "POSIX_THREADS",
55    "PTHREAD_BARRIER": "POSIX_BARRIERS",
56    "PTHREAD_COND": "POSIX_THREADS",
57    "PTHREAD_IPC": "POSIX_THREADS",
58    "PTHREAD_KEY": "POSIX_THREADS",
59    "PTHREAD_MUTEX": "POSIX_THREADS",
60    "PTHREAD_RWLOCK": "POSIX_READER_WRITER_LOCKS",
61    "PTHREAD_SPINLOCK": "POSIX_SPIN_LOCKS",
62    "TIMER": "POSIX_TIMERS",
63    "TIMER_DELAYTIMER_MAX": "POSIX_DELAYTIMER_MAX",
64    "SEM_NAMELEN_MAX": "POSIX_SEM_NAME_MAX",
65    "SEM_VALUE_MAX": "POSIX_SEM_VALUE_MAX",
66}
67
68MESSAGES = {
69    "POSIX_CLOCK":
70        "POSIX_CLOCK is a one-to-many replacement. If this simple substitution is not "
71        "sufficient, it's best to try a combination of POSIX_CLOCK_SELECTION, POSIX_CPUTIME, "
72        "POSIX_MONOTONIC_CLOCK, POSIX_TIMERS, and POSIX_TIMEOUTS.",
73    "POSIX_MAX_FDS":
74        "A read-only version of this symbol is POSIX_OPEN_MAX, which is of course, the standard "
75        "symbol. ZVFS_OPEN_MAX may be set by the user. Consider using POSIX_MAX_FDS if the "
76        "use-case is read-only.",
77}
78
79
80def process_file(path):
81    modified = False
82    output = []
83
84    try:
85        with open(path) as f:
86            lines = f.readlines()
87
88            for line in lines:
89                longest = ""
90                length = 0
91                for m in REPLACEMENTS:
92                    if re.match(".*" + m + ".*", line) and len(m) > length:
93                        length = len(m)
94                        longest = m
95
96                if length != 0:
97                    modified = True
98                    line = line.replace(longest, REPLACEMENTS[longest])
99                    msg = MESSAGES.get(longest)
100                    if msg:
101                        print(
102                            f"Notice: {longest} -> {REPLACEMENTS[longest]}: {msg}")
103
104                output.append(line)
105
106        if modified is False:
107            return
108
109        with open(path, "w") as f:
110            f.writelines(output)
111
112    except UnicodeDecodeError:
113        print(f"Unable to read lines from {path}", file=sys.stderr)
114    except Exception as e:
115        print(f"Failed with exception {e}", e)
116
117
118def process_tree(project):
119    for p in project.glob("**/*"):
120        for fp in FILE_PATTERNS:
121            cfp = re.compile(".+/" + fp + "$")
122            if re.match(cfp, str(p)) is not None:
123                process_file(p)
124
125
126if __name__ == "__main__":
127    parser = argparse.ArgumentParser(allow_abbrev=False)
128    parser.add_argument(
129        "-r",
130        "--root",
131        type=Path,
132        required=True,
133        help="Zephyr-based project path")
134    args = parser.parse_args()
135
136    process_tree(args.root)
137