SAE Teensy ECU
IIT SAE Microcontroller programming
Loading...
Searching...
No Matches
Pre_Build.py
Go to the documentation of this file.
1"""
2@file Pre_Build.py
3@author IR
4@brief This script preprocesses source files for use with Log_t
5@version 0.1
6@date 2020-11-11
7
8@copyright Copyright (c) 2022
9
10This script works by first duplicating source files to the build folder. \n
11Then it scans each file for calls to a Log function and modifies them as follows. \n
12
13If the call has a string for `LOG_TAG` parameter, give that string a unique integer ID and replace it with that integer. \n
14If the ID is not a string, leave the variable alone. \n
15
16Replace the call's `LOG_MSG` string with a unique ID as well. \n
17NOTE: The `LOG_MSG` parameter must always be an inline string. \n
18
19`LOG_TAG`s and `LOG_MSG`s do not share IDs. \n
20
21Eg.
22```
23Log(ID, "My Message"); -> Log(ID, 1);
24Log("My Str ID", "My Message"); -> Log(1, 1);
25```
26
27Calls to Log functions also have the option to send a number with a third parameter. \n
28
29Eg.
30```
31Log("My Str ID", "My Message", 56); -> Log(1, 1, 56);
32Log(ID, "My Message", A_Num_Var); -> Log(ID, 1, A_Num_Var);
33```
34
35Declarations of `LOG_TAG` also have their strings replaced with a unique ID. \n
36NOTE: Definition of `LOG_TAG`s must always be an inline string. \n
37
38Eg.
39```
40LOG_TAG TAG = "Logging Tag"; -> LOG_TAG TAG = 2;
41```
42
43A special case has been made to also allocate and replace string that call the following macro
44
45```
46_LogPrebuildString(x)
47```
48
49Where x is the string, it will be given a unique ID and replaced with said ID as if it were being called by a Logging function.
50This is useful where one wishes to generate log functions using the C preprocessor.
51Note, however, if this script is not run the macro should still allow everything to compile normally, leaving the string untouched
52
53"""
54
55# @cond
56
57import pathlib
58import shutil
59import os
60import asyncio
61import threading
62import time
63import sys
64import json
65
66from os.path import join as join_path
67from typing import Callable
68import vs_conf
69from vs_conf import Settings
70
71import script.util as Util
72import script.text as Text
73import script.id_matcher as IDMatch
74from script.file_entry import FileEntry
75from script.progress_bar import ProgressBar
76
77SOURCE_NAME = "src"
78LIB_PATH = join_path("libraries", "Log") # Path to the implementation of Log
79LIBRARIES_NAME = "libraries"
80WORKING_DIRECTORY_OFFSET = join_path("build", "Pre_Build", "")
81FILE_OUTPUT_PATH = "log_lookup.json"
82
83BYPASS_SCRIPT = os.path.exists("script.disable") # bypass script if this file is found
84
85MAX_RESULT = 8 # The maximum number of results to print out for errors and modified files
86
87SOURCE_DEST_NAME = f"{WORKING_DIRECTORY_OFFSET}{SOURCE_NAME}"
88LIBRARIES_DEST_NAME = f"{WORKING_DIRECTORY_OFFSET}{LIBRARIES_NAME}"
89
90INCLUDED_FILE_TYPES = (".c", ".cpp", ".h", ".hpp", ".t", ".tpp", ".s", ".def")
91
92
93async def ingest_files(progress_func: Callable[[None], None], entries: tuple[FileEntry]) -> None:
94 for file in entries[0]:
95 progress_func()
96 try:
97 await file.scan()
98 except Exception as e:
99 file.newError(e, "Thread Error", file.name)
100
101
102def run_ingest_files(progress_func: Callable[[None], None], *entries: FileEntry) -> None:
103 asyncio.run(ingest_files(progress_func, entries))
104
105
106Files: set[FileEntry] = set()
107FileRefs = set()
108Threads = set()
109Excluded_dirs = set()
110
111
112def allocate_files(path: str, offset: str) -> None:
113 blacklist = Util.get_library_blacklist()
114 model: dict[str, str]
115
116 try:
117 model = vs_conf.load_json()[Settings.CORE_NAME.key]
118 except json.JSONDecodeError:
119 sys.exit(Text.error("Error loading settings file, consider running the 'VS Setup' task"))
120
121 if model in blacklist:
122 blacklist = blacklist[model]
123 else:
124 blacklist = []
125
126 for subdir, _, files in os.walk(path):
127 cont = False
128 for directory in blacklist:
129 if str(subdir).startswith(directory):
130 Excluded_dirs.add(directory)
131 cont = True
132 break
133 if cont:
134 continue
135 for filename in files:
136 if pathlib.Path(filename).suffix.lower() not in INCLUDED_FILE_TYPES:
137 continue
138 filepath = join_path(subdir, filename)
139 rawpath = subdir + os.sep
140 if BYPASS_SCRIPT or rawpath.startswith(LIB_PATH): # Skip log module related files
141 Util.sync_file(filepath, offset, rawpath, suppress=True)
142 continue
143 file_entry = FileEntry(rawpath, filepath, filename, offset)
144 Files.add(file_entry)
145 FileRefs.add(file_entry)
146
147 for directory in blacklist:
148 rm_path = join_path(offset, directory)
149 if os.path.exists(rm_path):
150 shutil.rmtree(rm_path)
151
152
153def dole_files(count: int, progress_func: Callable[[None], None]) -> None:
154 while True:
155 file_set: set[FileEntry] = set()
156
157 i = 0
158
159 while len(Files) != 0 and i != count:
160 file_set.add(Files.pop())
161 i += 1
162
163 if len(file_set) != 0: # IMPROVE: Use actual mutlithreading
164 Threads.add(threading.Thread(target=run_ingest_files, args=(progress_func, file_set)))
165
166 if len(Files) == 0:
167 break
168
169
170def begin_scan() -> None:
171 for t in Threads:
172 t.start()
173
174 for t in Threads:
175 t.join()
176
177 IDMatch.clear_blanks()
178
179
180def printResults() -> None:
181 c = 0
182 m = 0
183
184 extStr: str = ""
185
186 for dir in Excluded_dirs:
187 if c < MAX_RESULT:
188 extStr += f" {dir}\n"
189 c += 1
190 else:
191 m += 1
192
193 if c > 0:
194 print(Text.header("\nExcluded Folders:"))
195 print(Text.yellow(extStr.strip("\n")))
196 if m > 0:
197 print(Text.underline(Text.yellow(" {} more folder{}".format(m, "s" if m > 1 else ""))))
198
199 c = 0
200 m = 0
201
202 extStr: str = ""
203
204 for f in FileRefs:
205 if f.modified:
206 if c < MAX_RESULT:
207 extStr += f" {f.name}\n"
208 c += 1
209 else:
210 m += 1
211 if len(f.errors) > 0:
212 Files.add(f)
213
214 if c > 0:
215 print(Text.header("\nModified Files:"))
216 print(Text.green(extStr.strip("\n")))
217 if m > 0:
218 print(Text.underline(Text.green(" {} more file{}".format(m, "s" if m > 1 else ""))))
219
220 sys.stdout.flush()
221
222 c = 0
223 m = 0
224
225 extStr: str = ""
226
227 for f in Files:
228 for e in f.errors:
229 if c < MAX_RESULT:
230 extStr += e
231 c += 1
232 else:
233 m += 1
234
235 if c > 0:
236 print(Text.header("\nFile Errors:"))
237 print(extStr.strip("\n"))
238
239 if m > 0:
240 print(Text.underline(Text.red(" {} more error{}".format(m, "s" if m > 1 else ""))))
241
242
243# Start Script
244def main() -> None:
245 Util.check_git_submodules(INCLUDED_FILE_TYPES)
246
247 Util.touch(SOURCE_DEST_NAME)
248 Util.touch(LIBRARIES_DEST_NAME)
249
250 allocate_files(SOURCE_NAME, WORKING_DIRECTORY_OFFSET)
251 allocate_files(LIBRARIES_NAME, WORKING_DIRECTORY_OFFSET)
252
253 if not BYPASS_SCRIPT:
254 time.sleep(0.5) # Let terminal settle
255
256 print(Text.warning(f"Available Ram: {Util.available_ram()} GBs\n"))
257
258 prehash = Util.hashFile(FILE_OUTPUT_PATH)
259
260 print(f"Files to search: {len(FileRefs)}")
261
262 prog = ProgressBar(len(Files), Text.important("Completed Files:"))
263
264 dole_files(8, prog.progress)
265
266 print(f"Threads to run: {len(Threads)}\n\n")
267
268 prog.start()
269 begin_scan()
270 prog.finish()
271
272 printResults()
273 IDMatch.save_lookup(FILE_OUTPUT_PATH)
274 newhash = Util.hashFile(FILE_OUTPUT_PATH)
275 if Util.FILES_CHANGED:
276 print(Text.important("\nNote: Files have changed, rebuild inbound"))
277 if newhash != prehash:
278 print(Text.really_important("\nNote: Mapped values have changed"))
279
280 print()
281
282 Util.encode_log_map(f"{WORKING_DIRECTORY_OFFSET}{LIB_PATH}")
283
284
285# TODO: remove files/modules that no longer exist in actual directories
286
287# try:
288# shutil.rmtree(SOURCE_DEST_NAME)
289# shutil.rmtree(LIBRARIES_DEST_NAME)
290# except FileNotFoundError:
291# pass
292
293
294if __name__ == "__main__":
295 main()
296
297# @endcond
Definition file_entry.py:1
Progress bar.
Text styling functions.
Definition text.py:1
Misc.
Definition util.py:1
dict[str, str] load_json()
Load the settings JSON.
Definition vs_conf.py:77