Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 29 additions & 10 deletions ner_environment/build_system/build_system.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,17 +61,29 @@ def build(profile: str = typer.Option("release", "--profile", "-p", help="Specif
if is_cmake: # Repo uses CMake, so execute CMake commands.
print("[#cccccc](ner build):[/#cccccc] [blue]CMake-based project detected.[/blue]")
if clean:
run_command_docker('cmake --build build --target clean ; find . -type d -name "build" -exec rm -rf {} +')
returncode = run_command_docker('cmake --build build --target clean ; find . -type d -name "build" -exec rm -rf {} +')
Copy link

Copilot AI Feb 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The clean command uses ; between cmake --build ... clean and find ... rm -rf .... When run via sh -c, the overall return code will be from the last command (find), which can mask a failing cmake clean and defeat the goal of propagating build errors. Use && (or otherwise preserve the first command’s exit code) so failures reliably propagate.

Suggested change
returncode = run_command_docker('cmake --build build --target clean ; find . -type d -name "build" -exec rm -rf {} +')
returncode = run_command_docker('cmake --build build --target clean && find . -type d -name "build" -exec rm -rf {} +')

Copilot uses AI. Check for mistakes.
print("[#cccccc](ner build):[/#cccccc] [green]Ran build-cleaning command.[/green]")
if returncode != 0:
sys.exit(returncode)
else:
run_command_docker(f"mkdir -p build && cd build && cmake -DCMAKE_BUILD_TYPE={profile.capitalize()} -DCMAKE_TOOLCHAIN_FILE=cmake/gcc-arm-none-eabi.cmake .. && cmake --build .", stream_output=True)
run_command_docker('chmod 777 -R ./build/*')
returncode = run_command_docker(f"mkdir -p build && cd build && cmake -DCMAKE_BUILD_TYPE={profile.capitalize()} -DCMAKE_TOOLCHAIN_FILE=cmake/gcc-arm-none-eabi.cmake .. && cmake --build .", stream_output=True)
if returncode != 0:
sys.exit(returncode)
returncode = run_command_docker('chmod 777 -R ./build/*')
if returncode != 0:
sys.exit(returncode)
Comment on lines +69 to +74
Copy link

Copilot AI Feb 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

returncode here can currently be None because run_command() doesn’t return 0 on success in all paths (e.g., stream_output=True or commands with empty stdout like chmod). Comparing None != 0 will be true and will trigger sys.exit(None) (exit 0) early, skipping subsequent steps (e.g., chmod/fix_compile_commands). Fix run_command() to always return an int (0 on success) so these checks behave correctly.

Copilot uses AI. Check for mistakes.
else: # Repo uses Make, so execute Make commands.
print("[#cccccc](ner build):[/#cccccc] [blue]Makefile-based project detected.[/blue]")
if clean:
run_command_docker("make clean", stream_output=True)
returncode = run_command_docker("make clean", stream_output=True)
if returncode != 0:
sys.exit(returncode)

else:
run_command_docker(f"make -j{os.cpu_count()}", stream_output=True)
returncode = run_command_docker(f"make -j{os.cpu_count()}", stream_output=True)
if returncode != 0:
sys.exit(returncode)


if not clean and is_cmake:
fix_compile_commands()
Expand Down Expand Up @@ -346,9 +358,9 @@ def usbip(connect: bool = typer.Option(False, "--connect", help="Connect to a US
# Helper functions - not direct commands
# ==============================================================================

def run_command(command, stream_output=False, exit_on_fail=False):
"""Run a shell command. Optionally stream the output in real-time."""

def run_command(command, stream_output=False, exit_on_fail=False) -> int:
# print("guess who isn't fucked rn") # REMOVEME
"""Run a shell command. Optionally stream the output in real-time. Returns the returncode of the command called"""
if stream_output:

process = subprocess.Popen(command, text=True)
Expand All @@ -358,17 +370,24 @@ def run_command(command, stream_output=False, exit_on_fail=False):
print(f"Error: Command exited with code {returncode}", file=sys.stderr)
if exit_on_fail:
sys.exit(returncode)
# return returncode

else:
return returncode

else:
try:
result = subprocess.run(command, check=True, capture_output=True, text=True)
if result.stdout and result.stdout.strip(): # Only print if stdout is not empty or just whitespace
print(result.stdout)
return 0 # Code should be zero if it reaches this point
Comment on lines 378 to +383
Copy link

Copilot AI Feb 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

run_command() is annotated to return int, but it returns None on success in multiple cases: (1) stream_output=True with a zero exit status, and (2) stream_output=False when the command succeeds but produces no stdout. This breaks callers that rely on the return code (now including build). Ensure the function returns 0 on all successful paths and the actual non-zero code on failure.

Suggested change
else:
try:
result = subprocess.run(command, check=True, capture_output=True, text=True)
if result.stdout and result.stdout.strip(): # Only print if stdout is not empty or just whitespace
print(result.stdout)
return 0 # Code should be zero if it reaches this point
# Successful execution with zero exit status
return 0
else:
try:
result = subprocess.run(command, check=True, capture_output=True, text=True)
if result.stdout and result.stdout.strip(): # Only print if stdout is not empty or just whitespace
print(result.stdout)
# Command succeeded (check=True did not raise), so return success code
return 0 # Code should be zero if it reaches this point

Copilot uses AI. Check for mistakes.
except subprocess.CalledProcessError as e:
print(f"Error occurred: {e}", file=sys.stderr)
print(e.stderr, file=sys.stderr)
if exit_on_fail:
sys.exit(e.returncode)
return e.returncode


def fix_compile_commands():
'''
Expand Down Expand Up @@ -421,11 +440,11 @@ def fix_compile_commands():

print('Successfully patched compile_commands.json')

def run_command_docker(command, stream_output=False):
def run_command_docker(command, stream_output=False) -> int:
"""Run a command in the Docker container."""
docker_command = ["docker", "compose", "run", "--rm", "ner-gcc-arm", "sh", "-c", command]
print(f"[bold blue](ner-gcc-arm): Running command '{command}' in Docker container.")
run_command(docker_command, stream_output=stream_output)
return run_command(docker_command, stream_output=stream_output)
Comment on lines +443 to +447
Copy link

Copilot AI Feb 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR description mentions ner test returning the Docker command exit code, but only build() checks and exits based on run_command_docker()’s return value. Since Typer/Click ignores a command function’s return value, test() will still exit 0 unless it explicitly raises typer.Exit(code=...) / sys.exit(...) on non-zero return codes. Update test() accordingly (including the --clean path if desired).

Copilot uses AI. Check for mistakes.

def disconnect_usbip():
"""Disconnect the current USB device."""
Expand Down
Loading