mirror of
https://github.com/weechat/weechat.git
synced 2026-06-12 14:14:48 +02:00
333 lines
11 KiB
Python
Executable File
333 lines
11 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
#
|
|
# SPDX-FileCopyrightText: 2023-2025 Sébastien Helleu <flashcode@flashtux.org>
|
|
#
|
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
#
|
|
# This program is free software; you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation; either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
#
|
|
|
|
"""
|
|
Check if Curl symbols defined in src/core/core-url.c are matching symbols
|
|
defined in Curl (introduced/deprecated/last versions), using this file:
|
|
https://github.com/curl/curl/blob/master/docs/libcurl/symbols-in-versions.
|
|
|
|
File symbols-in-versions must be passed as stdin to the script.
|
|
|
|
Usage example:
|
|
|
|
```
|
|
URL=https://raw.githubusercontent.com/curl/curl/master/docs/libcurl/symbols-in-versions
|
|
curl $URL | ./check_curl_symbols.py
|
|
```
|
|
|
|
Or with Curl repository cloned locally:
|
|
|
|
```
|
|
./check_curl_symbols.py < /path/to/curl/docs/libcurl/symbols-in-versions
|
|
```
|
|
|
|
This script requires Python 3.7+.
|
|
"""
|
|
|
|
from dataclasses import dataclass
|
|
from pathlib import Path
|
|
from typing import Dict, List, TextIO, Tuple
|
|
|
|
import re
|
|
import sys
|
|
|
|
SRC_PATH = (
|
|
Path(__file__).resolve().parent.parent / "src" / "core" / "core-url.c"
|
|
)
|
|
|
|
# NOTE: keep version in sync with CMakeLists.txt
|
|
CURL_MIN_VERSION_STR = "7.47.0"
|
|
|
|
WEECHAT_CURL_MIN_VERSION_RE = (
|
|
r"#if LIBCURL_VERSION_NUM >= (?P<hex_min_version>0x[0-9A-F]+) "
|
|
r"/\* (?P<str_min_version>[0-9][0-9.]+) \*/"
|
|
)
|
|
|
|
WEECHAT_CURL_MAX_VERSION_RE = (
|
|
r"#if LIBCURL_VERSION_NUM < (?P<hex_max_version>0x[0-9A-F]+) "
|
|
r"/\* < (?P<str_max_version>[0-9][0-9.]+) \*/"
|
|
)
|
|
|
|
WEECHAT_CURL_MIN_MAX_VERSION_RE = (
|
|
r"#if LIBCURL_VERSION_NUM >= (?P<hex_min_version>0x[0-9A-F]+) "
|
|
r"&& LIBCURL_VERSION_NUM < (?P<hex_max_version>0x[0-9A-F]+) "
|
|
r"/\* (?P<str_min_version>[0-9][0-9.]+) < "
|
|
r"(?P<str_max_version>[0-9][0-9.]+) \*/"
|
|
)
|
|
WEECHAT_ENDIF_RE = r"#endif"
|
|
WEECHAT_CURL_CONSTANT_RE = (
|
|
r" URL_DEF_CONST\((?P<prefix>[A-Z0-9_]+), (?P<name>[A-Z0-9_]+)\),"
|
|
)
|
|
WEECHAT_CURL_OPTION_RE = (
|
|
r" URL_DEF_OPTION\((?P<name>[A-Z0-9_]+), .*\),"
|
|
)
|
|
CURL_SYMBOL_RE = r"[A-Z][A-Z0-9_]"
|
|
|
|
|
|
@dataclass
|
|
class WeechatCurlSymbol:
|
|
"""A Curl symbol declared in WeeChat."""
|
|
|
|
name: str
|
|
min_curl: int = 0
|
|
max_curl: int = 0
|
|
line_no: int = 0
|
|
|
|
|
|
def curl_version_to_int(version: str) -> int:
|
|
"""
|
|
Convert Curl version as string to integer.
|
|
|
|
:param version: version as string (eg: "7.87.0")
|
|
:return: version as integer, eg: 481024 (== 0x075700, 0x57 == 87)
|
|
"""
|
|
if version == "-":
|
|
return 0
|
|
result = 0
|
|
items = version.split(".")
|
|
if len(items) < 3:
|
|
items.append("0")
|
|
factor = 0
|
|
for item in reversed(items):
|
|
result += int(item) << factor
|
|
factor += 8
|
|
return result
|
|
|
|
|
|
def curl_version_to_str(version: int) -> str:
|
|
"""
|
|
Convert Curl version as integer to string.
|
|
|
|
:param version: version as integer, eg: 481024 (0x075700)
|
|
:return: version as string, eg: "7.87.0"
|
|
"""
|
|
if version == 0:
|
|
return "-"
|
|
result = ""
|
|
while version > 0:
|
|
result = str(version & 0xFF) + "." + result
|
|
version = version >> 8
|
|
return result.rstrip(".")
|
|
|
|
|
|
def get_curl_symbols(symbols_file: TextIO) -> Dict[str, Tuple[int, int]]:
|
|
"""
|
|
Parse file docs/libcurl/symbols-in-versions from Curl repository.
|
|
|
|
:param symbols_file: file with Curl symbols
|
|
:return: Curl symbols as dict: {name: (version_min, version_max)}
|
|
"""
|
|
curl_symbol_pattern = re.compile(CURL_SYMBOL_RE)
|
|
symbols: Dict[str, Tuple[int, int]] = {}
|
|
if symbols_file.isatty():
|
|
return symbols
|
|
for line in symbols_file:
|
|
match = re.match(curl_symbol_pattern, line)
|
|
if match:
|
|
name, intro, deprec, last, *_ = (line + " - -").split()
|
|
v_max = last if last != "-" else deprec
|
|
symbols[name] = (
|
|
curl_version_to_int(intro),
|
|
curl_version_to_int(v_max),
|
|
)
|
|
return symbols
|
|
|
|
|
|
def check_req_symbols(symbols: List[WeechatCurlSymbol]) -> int:
|
|
"""
|
|
Checks the symbols' min/max version, relative to minimum Curl version that
|
|
we require.
|
|
|
|
:return: errors
|
|
"""
|
|
errors: int = 0
|
|
req_curl = curl_version_to_int(CURL_MIN_VERSION_STR)
|
|
for symbol in symbols:
|
|
if symbol.min_curl and symbol.min_curl <= req_curl:
|
|
print(
|
|
f"{SRC_PATH}:{symbol.line_no}: min version for "
|
|
f"symbol {symbol.name} older than minimal required "
|
|
f"curl {req_curl}. Remove if guard."
|
|
)
|
|
errors += 1
|
|
if symbol.max_curl and symbol.max_curl < req_curl:
|
|
print(
|
|
f"{SRC_PATH}:{symbol.line_no}: max version for "
|
|
f"symbol {symbol.name} older than minimal required "
|
|
f"curl {req_curl}. Remove the symbol."
|
|
)
|
|
errors += 1
|
|
return errors
|
|
|
|
|
|
def get_weechat_curl_symbols() -> Tuple[List[WeechatCurlSymbol], int]:
|
|
"""
|
|
Parse Curl symbols declared in src/core/core-url.c.
|
|
|
|
:return: tuple (list_symbols, errors)
|
|
"""
|
|
# pylint: disable=too-many-locals,too-many-statements
|
|
min_version_pattern = re.compile(WEECHAT_CURL_MIN_VERSION_RE)
|
|
max_version_pattern = re.compile(WEECHAT_CURL_MAX_VERSION_RE)
|
|
min_max_version_pattern = re.compile(WEECHAT_CURL_MIN_MAX_VERSION_RE)
|
|
endif_pattern = re.compile(WEECHAT_ENDIF_RE)
|
|
constant_pattern = re.compile(WEECHAT_CURL_CONSTANT_RE)
|
|
option_pattern = re.compile(WEECHAT_CURL_OPTION_RE)
|
|
v_min: int = 0
|
|
v_max: int = 0
|
|
symbols: List[WeechatCurlSymbol] = []
|
|
errors: int = 0
|
|
line_no: int = 0
|
|
with open(SRC_PATH, encoding="utf-8") as src_file:
|
|
for line in src_file:
|
|
line_no += 1
|
|
# min Curl version
|
|
match = re.match(min_version_pattern, line)
|
|
if match:
|
|
hex_min_vers = match["hex_min_version"]
|
|
str_min_vers = match["str_min_version"]
|
|
v_min, v_max = int(hex_min_vers, 0), 0
|
|
comment_min = curl_version_to_int(str_min_vers)
|
|
if v_min != comment_min:
|
|
print(
|
|
f"{SRC_PATH}:{line_no}: min version not matching "
|
|
f"the comment: "
|
|
f"{hex_min_vers} != {str_min_vers}"
|
|
)
|
|
errors += 1
|
|
continue
|
|
# max Curl version
|
|
match = re.match(max_version_pattern, line)
|
|
if match:
|
|
hex_max_vers = match["hex_max_version"]
|
|
str_max_vers = match["str_max_version"]
|
|
v_min, v_max = 0, int(hex_max_vers, 0)
|
|
comment_max = curl_version_to_int(str_max_vers)
|
|
if v_max != comment_max:
|
|
print(
|
|
f"{SRC_PATH}:{line_no}: max version not matching "
|
|
f"the comment: "
|
|
f"{hex_max_vers} != {str_max_vers}"
|
|
)
|
|
errors += 1
|
|
continue
|
|
# min + max Curl version
|
|
match = re.match(min_max_version_pattern, line)
|
|
if match:
|
|
hex_min_vers = match["hex_min_version"]
|
|
hex_max_vers = match["hex_max_version"]
|
|
str_min_vers = match["str_min_version"]
|
|
str_max_vers = match["str_max_version"]
|
|
v_min, v_max = int(hex_min_vers, 0), int(hex_max_vers, 0)
|
|
comment_min = curl_version_to_int(str_min_vers)
|
|
comment_max = curl_version_to_int(str_max_vers)
|
|
if v_min != comment_min:
|
|
print(
|
|
f"{SRC_PATH}:{line_no}: min version not matching "
|
|
f"the comment: "
|
|
f"{hex_min_vers} != {str_min_vers}"
|
|
)
|
|
errors += 1
|
|
if v_max != comment_max:
|
|
print(
|
|
f"{SRC_PATH}:{line_no}: max version not matching "
|
|
f"the comment: "
|
|
f"{hex_max_vers} != {str_max_vers}"
|
|
)
|
|
errors += 1
|
|
continue
|
|
# end of min/max Curl version
|
|
match = re.match(endif_pattern, line)
|
|
if match:
|
|
v_min, v_max = 0, 0
|
|
continue
|
|
# Curl constant
|
|
match = re.match(constant_pattern, line)
|
|
if match:
|
|
name = f"CURL{match['prefix']}_{match['name']}"
|
|
symbols.append(WeechatCurlSymbol(name, v_min, v_max, line_no))
|
|
continue
|
|
# Curl option
|
|
match = re.match(option_pattern, line)
|
|
if match:
|
|
name = f"CURLOPT_{match['name']}"
|
|
symbols.append(WeechatCurlSymbol(name, v_min, v_max, line_no))
|
|
continue
|
|
|
|
errors += check_req_symbols(symbols)
|
|
return symbols, errors
|
|
|
|
|
|
def check_symbols(
|
|
weechat_curl_symbols: List[WeechatCurlSymbol],
|
|
curl_symbols: Dict[str, Tuple[int, int]],
|
|
) -> int:
|
|
"""
|
|
Check that symbols declared are matching Curl symbols.
|
|
|
|
:param weechat_curl_symbols: list of Curl symbols in WeeChat
|
|
:param curl_symbols: list of all Curl symbols
|
|
"""
|
|
req_curl = curl_version_to_int(CURL_MIN_VERSION_STR)
|
|
to_str = curl_version_to_str
|
|
errors = 0
|
|
for symbol in weechat_curl_symbols:
|
|
curl_symbol = curl_symbols.get(symbol.name)
|
|
if not curl_symbol:
|
|
print(
|
|
f"{SRC_PATH}:{symbol.line_no}: symbol {symbol.name} "
|
|
f"not found in Curl"
|
|
)
|
|
errors += 1
|
|
continue
|
|
if curl_symbol[0] > req_curl and symbol.min_curl != curl_symbol[0]:
|
|
print(
|
|
f"{SRC_PATH}:{symbol.line_no}: min version for "
|
|
f"symbol {symbol.name} differs: "
|
|
f"{to_str(symbol.min_curl)} in WeeChat, "
|
|
f"{to_str(curl_symbol[0])} in Curl"
|
|
)
|
|
errors += 1
|
|
if curl_symbol[1] >= req_curl and symbol.max_curl != curl_symbol[1]:
|
|
print(
|
|
f"{SRC_PATH}:{symbol.line_no}: max version for "
|
|
f"symbol {symbol.name} differs: "
|
|
f"{to_str(symbol.max_curl)} in WeeChat, "
|
|
f"{to_str(curl_symbol[1])} in Curl"
|
|
)
|
|
errors += 1
|
|
return errors
|
|
|
|
|
|
def main() -> int:
|
|
"""Check Curl symbols and return the number of errors found."""
|
|
curl_symbols = get_curl_symbols(sys.stdin)
|
|
if not curl_symbols:
|
|
sys.exit("FATAL: failed to read Curl symbols on standard input")
|
|
weechat_curl_symbols, errors = get_weechat_curl_symbols()
|
|
errors += check_symbols(weechat_curl_symbols, curl_symbols)
|
|
dict_err = {0: "all good!", 1: "1 error"}
|
|
print("Curl symbols:", dict_err.get(errors, f"{errors} errors"))
|
|
return errors
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(min(main(), 255))
|