2 from argparse
import ArgumentParser
, ArgumentDefaultsHelpFormatter
3 from hashlib
import md5
11 HEADER_FILE_TEMPLATE
= """{license_header}
17 #include "common/streambuf.h"
21 void sbufWriteBuildInfoFlags(sbuf_t *dst);
24 SOURCE_FILE_TEMPLATE
= """{license_header}
32 #include "common/streambuf.h"
34 #include "msp/msp_build_info.h"
36 void sbufWriteBuildInfoFlags(sbuf_t *dst)
38 static const uint16_t options[] = {
42 for (unsigned i = 0; i < ARRAYLEN(options); i++)
44 sbufWriteU16(dst, options[i]);
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`
55 * Input hash : {input_hash}
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:
71 spaceless
= s
.replace(" ", "")
72 return "".join([(c
if not c
.isupper() else f
" {c}") for c
in spaceless
]) \
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}")
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")
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 \
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
123 for option_name
, option_value
, group
in options
:
124 if group
!= last_group
:
125 lines
.append(f
"// {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
))
134 logging
.info(f
"Written header file: {msp_build_info_h_path}")
136 with
open(msp_build_info_c_path
, "w+") as f
:
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
))
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
)
167 root_path
=PROJECT_ROOT_DIR
,
168 target_path
=args
.target_dir
,
169 endpoint_url
=args
.endpoint_url
,