Add altitude variant for OSD (#13716)
[betaflight.git] / src / utils / make-build-info.py
blobfcb468b12499140e756ec264111e0650cfe28c9e
1 #!/usr/bin/env python3
2 from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter
3 from hashlib import md5
4 import json
5 import logging
6 import os
8 import requests
11 HEADER_FILE_TEMPLATE = """{license_header}
13 {generated_warning}
15 #pragma once
17 #include "common/streambuf.h"
19 {defines}
21 void sbufWriteBuildInfoFlags(sbuf_t *dst);
22 """
24 SOURCE_FILE_TEMPLATE = """{license_header}
26 {generated_warning}
28 #include <stdint.h>
30 #include "platform.h"
32 #include "common/streambuf.h"
34 #include "msp/msp_build_info.h"
36 void sbufWriteBuildInfoFlags(sbuf_t *dst)
38 static const uint16_t options[] = {
39 {build_options}
42 for (unsigned i = 0; i < ARRAYLEN(options); i++)
44 sbufWriteU16(dst, options[i]);
47 """
49 WARNING_COMMENT_TEMPLATE = """
51 * WARNING: This is an auto-generated file, please do not edit directly!
53 * Generator : `src/utils/make-build-info.py`
54 * Source : {source}
55 * Input hash : {input_hash}
57 """
60 def __find_project_root() -> str:
61 utils_dir = os.path.abspath(os.path.dirname(__file__))
62 src_dir = os.path.dirname(utils_dir)
63 root_dir = os.path.dirname(src_dir)
64 return os.path.realpath(root_dir)
67 def camel_case_to_title(s: str) -> str:
68 if not s:
69 return "Unspecified"
70 else:
71 spaceless = s.replace(" ", "")
72 return "".join([(c if not c.isupper() else f" {c}") for c in spaceless]) \
73 .lstrip() \
74 .title()
77 def fetch_build_options(endpoint_url: str) -> tuple:
78 logging.info(f"Fetching JSON: {endpoint_url}")
79 data = requests.get(endpoint_url, timeout=2).json()
80 input_hash = md5(json.dumps(data, sort_keys=True).encode()).hexdigest()
81 logging.info(f"Input hash: {input_hash}")
83 defines = []
84 options = []
85 groups = list(data.keys())
86 for group_index, option_list in enumerate(data.values()):
87 for option in option_list:
88 define = option.get("value")
89 if define:
90 defines.append(define)
91 number = option.get("key")
92 name = define.replace("USE_", "BUILD_OPTION_")
93 options.append((name, number, camel_case_to_title(groups[group_index])))
94 logging.info(f"Number of defines: {len(defines)}")
95 return defines, options, input_hash
98 def get_warning_comment(source: str, input_hash: str) -> str:
99 return WARNING_COMMENT_TEMPLATE \
100 .strip() \
101 .replace("{source}", source) \
102 .replace("{input_hash}", input_hash) \
105 def main(root_path: str, target_path: str, endpoint_url: str):
106 logging.info(f"Project root: {root_path}")
108 license_file_path = os.path.join(root_path, "DEFAULT_LICENSE.md")
109 msp_build_info_c_path = os.path.join(target_path, "msp_build_info.c")
110 msp_build_info_h_path = os.path.join(target_path, "msp_build_info.h")
112 with open(license_file_path) as f:
113 license_header = f.read().rstrip()
115 gates, options, input_hash = fetch_build_options(endpoint_url)
117 generated_warning = get_warning_comment(endpoint_url, input_hash)
119 with open(msp_build_info_h_path, "w+") as f:
120 max_len = max(map(lambda x: len(x[0]), options)) + 4
121 lines = []
122 last_group = None
123 for option_name, option_value, group in options:
124 if group != last_group:
125 lines.append(f"// {group}")
126 last_group = group
127 lines.append(f"#define {option_name:<{max_len}}{option_value}")
128 data = HEADER_FILE_TEMPLATE \
129 .replace("{license_header}", license_header) \
130 .replace("{generated_warning}", generated_warning) \
131 .replace("{defines}", "\n".join(lines))
132 f.write(data)
134 logging.info(f"Written header file: {msp_build_info_h_path}")
136 with open(msp_build_info_c_path, "w+") as f:
137 lines = []
138 indent = " " * 8
139 for i, define in enumerate(gates):
140 option_name, _, _ = options[i]
141 lines.append(f"#ifdef {define}")
142 lines.append(f"{indent}{option_name},")
143 lines.append("#endif")
144 data = SOURCE_FILE_TEMPLATE \
145 .replace("{license_header}", license_header) \
146 .replace("{generated_warning}", generated_warning) \
147 .replace("{build_options}", "\n".join(lines))
148 f.write(data)
150 logging.info(f"Written source file: {msp_build_info_c_path}")
153 if __name__ == "__main__":
154 PROJECT_ROOT_DIR = __find_project_root()
155 DEFAULT_TARGET_DIR = os.path.join(PROJECT_ROOT_DIR, "src", "main", "msp")
157 parser = ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter)
158 parser.add_argument("endpoint_url", help="URL to build options API endpoint")
159 parser.add_argument("-d", "--target-dir", default=DEFAULT_TARGET_DIR, help="Path to output directory")
160 parser.add_argument("-v", "--verbose", action="store_true")
162 args = parser.parse_args()
164 logging.basicConfig(level=logging.INFO if args.verbose else logging.ERROR)
166 main(
167 root_path=PROJECT_ROOT_DIR,
168 target_path=args.target_dir,
169 endpoint_url=args.endpoint_url,