-
Notifications
You must be signed in to change notification settings - Fork 82
ci: 👷 check that examples in header files compile #774
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop-pros-4
Are you sure you want to change the base?
Changes from 9 commits
810a55b
f28a981
8e68045
f0c2297
b0b8163
6caedfb
e1617a7
4f234b1
2d9a547
d32af1c
7b548e6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,119 @@ | ||
| import asyncio | ||
| import os | ||
| import re | ||
| import subprocess | ||
| import sys | ||
| import tempfile | ||
|
|
||
| include_directory = "include/pros" | ||
| precompiled_include_directory = "" | ||
| error_count = 0 | ||
| gcc_semaphore = asyncio.Semaphore(value=os.cpu_count()) | ||
|
|
||
| def print_and_exit(message): | ||
| print(f"Failed to check example code in pros headers:\n{message}", file=sys.stderr) | ||
| sys.exit(1) | ||
|
|
||
| def precompile_header(): | ||
| flags = [ | ||
| "-x", "c++-header", | ||
| "include/main.h", | ||
| "-std=c++23", | ||
| "-I", "include", | ||
| "-D", "_PROS_KERNEL_SUPPRESS_LLEMU_WARNING", | ||
| "-Wfatal-errors", | ||
| "-o", os.path.join(precompiled_include_directory, "main.h.gch") | ||
| ] | ||
| subprocess.run(["arm-none-eabi-gcc", *flags], check=True) | ||
|
|
||
| async def compile_code(code_text, filename, is_cpp): | ||
| """ | ||
| Compiles the given code, discarding the output file. | ||
|
|
||
| Args: | ||
| code_text (str): The C/C++ source code to compile. | ||
| is_cpp (bool): Whether the code is C++ or not. | ||
|
|
||
| Returns: | ||
| success (bool): Whether the compilation succeeded or not. | ||
| """ | ||
| flags = [ | ||
| "-x", "c++" if is_cpp else "c", | ||
| "-std=c++23" if is_cpp else "-std=c2x", | ||
| # Only need precompiled headers for C++ | ||
| *(("-I", precompiled_include_directory) if is_cpp else ()), | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. weird syntax trick lol maybe just do it after the list initialization |
||
| "-I", "include", | ||
| # Prevent spurious warnings | ||
| "-D", "_PROS_KERNEL_SUPPRESS_LLEMU_WARNING", | ||
| # Stop compilation on first error | ||
| "-Wfatal-errors", | ||
| # Read input from stdin | ||
| "-", | ||
| # Generate assembly (avoids linker errors) | ||
| "-S", | ||
|
Comment on lines
+52
to
+53
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. does the more standard -c not work? if not then probably give a longer comment to explain why youre doing -S because it took me a second for that to click
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. -c would probably work, but assembling the files seems like an unnecessary step.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok, maybe just a better comment - something like |
||
| # Discard the output file | ||
| "-o", os.devnull | ||
| ] | ||
| async with gcc_semaphore: | ||
| process = await asyncio.create_subprocess_exec( | ||
| "arm-none-eabi-gcc", | ||
| *flags, | ||
| stdin=asyncio.subprocess.PIPE, | ||
| stdout=asyncio.subprocess.PIPE, | ||
| stderr=asyncio.subprocess.PIPE | ||
| ) | ||
| stdout, stderr = await process.communicate(input=code_text.encode("utf-8")) | ||
| success = (process.returncode == 0) | ||
|
Comment on lines
+65
to
+66
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. interesting - where do you await the compilation being done? im not super familiar with this but im confused. does process.communicate() imply blocking to wait for a response? is there a timeout?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, communicate sends input via stdin and then waits for the process to finish running (no timeout though)
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ok maybe a comment that says that because its not obvious imo |
||
|
|
||
| if not success: | ||
| global error_count | ||
| error_count += 1 | ||
| print(f"=== example code from {filename} failed to compile ===") | ||
| print(code_text) | ||
| print("=== compiler output below: ===") | ||
| print(stderr.decode()) | ||
| print("========") | ||
|
|
||
| return success | ||
|
|
||
| def example_code_generator(): | ||
| try: | ||
| files = os.listdir(include_directory) | ||
| except: | ||
| print_and_exit(f"Error accessing directory '{include_directory}': {e}") | ||
| try: | ||
| for filename in files: | ||
| file_path = os.path.join(include_directory, filename) | ||
| if os.path.isfile(file_path): | ||
| try: | ||
| with open(file_path, "r") as f: | ||
| header_text = f.read() | ||
| is_cpp = filename.endswith(".hpp") | ||
| # This pattern matches all lines between the \code and \endcode markers | ||
| pattern = r"(^.+\\code\n)((.*\n)+?)(^.+\\endcode)" | ||
| for match in re.finditer(pattern, header_text, re.MULTILINE): | ||
| code_block = match.group(2) | ||
| lines = code_block.splitlines() | ||
| # Remove the leading * from each line | ||
| cleaned_lines = [re.sub(r"^\s*\* ?", "", line) for line in lines] | ||
| code_snippet = "#include \"main.h\"\n"+"\n".join(cleaned_lines) | ||
| yield code_snippet, filename, is_cpp | ||
| except Exception as e: | ||
| print_and_exit(f"Error reading file '{filename}': {e}") | ||
| except Exception as e: | ||
| print_and_exit(f"Error accessing directory '{include_directory}': {e}") | ||
|
|
||
| async def main(): | ||
| with tempfile.TemporaryDirectory() as tmpdirname: | ||
| global precompiled_include_directory | ||
| precompiled_include_directory = tmpdirname | ||
| precompile_header() | ||
| async with asyncio.TaskGroup() as tg: | ||
| tasks = [tg.create_task(compile_code(*item)) for item in example_code_generator()] | ||
| results = [task.result() for task in tasks] | ||
| global error_count | ||
| if error_count > 0: | ||
| print_and_exit(f"{error_count} compile errors encountered") | ||
|
|
||
| if __name__ == "__main__": | ||
| asyncio.run(main()) | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
huge nitpick (like, dont bother changing this) but for the record its UB to have any identifier (including macros) that start with underscore then a capital letter lol.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
actually idek if you wrote that macro or not but 🤷 just having fun being an asshole by mentioning it