Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
2d4d30d
Implemented build and argparse functionality for new srlow cli tool
ryan-wong157 Sep 8, 2026
bff5b8f
feat: made printing nicer
ryan-wong157 Sep 8, 2026
e821a4f
feat: implemented srlow build clean function
ryan-wong157 Sep 8, 2026
4232702
feat: refactored test and build, added test functionality
ryan-wong157 Sep 8, 2026
485ee75
fix: catch all exceptions when suboprocess fails now
ryan-wong157 Sep 8, 2026
db3b93b
feat: added installing to srlow, and updated readme
ryan-wong157 Sep 8, 2026
25777f5
fix: changed install build dir to be {preset}/build instead
ryan-wong157 Sep 10, 2026
3f545e4
fix: FML typo
ryan-wong157 Sep 11, 2026
fe5cd73
feat: added better headings when printing logs
ryan-wong157 Sep 11, 2026
154d517
feat: added nice header printing to srbuild as well
ryan-wong157 Sep 11, 2026
d8e8435
feat: implemented being able to run und codecheck using srlow
ryan-wong157 Sep 11, 2026
f1ec517
fix: added --preset flag for srlow analyse
ryan-wong157 Sep 11, 2026
cfc0c7d
feat: added UND_CONFIG_FILE and und license checking
ryan-wong157 Sep 11, 2026
309ac8e
chore: devnulled isundlicensed command output, added exception printing
ryan-wong157 Sep 11, 2026
2703f9d
chore: {label}ing doesnt work for analyse-ing
ryan-wong157 Sep 11, 2026
d195867
fix: left a variable in {} when it shouldnt be..
ryan-wong157 Sep 11, 2026
fcbeaf3
feat: add error handling if preset is wrong, or build dir is not ther…
ryan-wong157 Sep 11, 2026
8d57e12
chore: print formatting
ryan-wong157 Sep 11, 2026
3167595
chore: change pass fail printing
ryan-wong157 Sep 11, 2026
096427a
fix: preset path should check for dir, not file
ryan-wong157 Sep 11, 2026
8401b13
chore: changed some und output file names
ryan-wong157 Sep 11, 2026
bd38aa6
feat: improved analyse_one() to use a loop and separate error logging
ryan-wong157 Sep 11, 2026
6d54f13
feat: now allows glob patterns in analyse.txt because und doesn't for…
ryan-wong157 Sep 11, 2026
f2077f5
feat: analyse.txt supports inline comments now
ryan-wong157 Sep 11, 2026
8b0a214
feat: changed all prints to use the logging module instead
ryan-wong157 Sep 11, 2026
f404ea8
feat: changed all prints to logging
ryan-wong157 Sep 11, 2026
4834313
fix: failing test
ryan-wong157 Sep 11, 2026
10bd049
feat: fixed version
ryan-wong157 Sep 11, 2026
43bfda6
chore: updated readme
ryan-wong157 Sep 11, 2026
e2590a0
fix: Typos and such
ryan-wong157 Sep 12, 2026
c0ae8a7
fix: changed analysis output dir and sarif file name to just be "anal…
ryan-wong157 Sep 14, 2026
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
99 changes: 94 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
# Sunswift Dev Tools

Developer tooling for SR-Mjolnir and SR-Gungnir, SR8's high level repositories.
Developer tooling for SR-Mjolnir and SR-Gungnir, SR8's high level repositories. Also now includes tooling for SR-Amsvartnir, SR8's firmware repository.
Includes:

- `srpkg`: creates a new DDS package in your current working directory
- `srbuild`: wraps CMake to configure, build, and install targets
- `srlow`: helper for building, ceedling unit testing and Understand static analysis

Both tools are installed as a [uv](https://docs.astral.sh/uv/) tool.
Tools are installed as a [uv](https://docs.astral.sh/uv/) tool.

`srlaunch` (process launcher) has moved to [`deprecated/`](deprecated/). QNX targets should use QNX's own process management. It is being replaced by a cross-platform orchestrator (Seb's thesis).

Expand All @@ -18,7 +19,7 @@ Install globally as a uv tool (recommended):
uv tool install git+https://github.com/UNSW-Sunswift/SR-Dev-Tools.git
```

This puts `srbuild` and `srpkg` on your PATH. To upgrade later:
This puts all CLI tools on your PATH. To upgrade later:

```bash
uv tool upgrade sr-dev-tools
Expand Down Expand Up @@ -119,8 +120,87 @@ srbuild target node1 -j 16

Defaults to 8 parallel jobs.

## Example workflow
## `srlow`

Helper for CMake, Ceedling and Understand to build, test and analyse the STM32 firmware modules in SR-Amsvartnir.

### Repository root discovery

`srlow` looks for a `.sunswift-firmware` marker file, walking up from your current directory. The nearest directory containing it is the root, and must contain a `src/` directory holding one subdirectory per STM32 module.

Each module directory must have its own `CMakeLists.txt` and `CMakePresets.json` at its top level. A module may also contain a `Test/` directory, which must be a Ceedling project.

### Building

```bash
# Build and install every module
srlow build all --preset [Debug | Release]

# Build and install specific modules (directory names)
srlow build target module1 module2 --preset debug

# Delete every module's build/ directory
srlow build clean
```

`--preset` / `-p` is required for `all` and `target`, and names a CMake preset defined in each module's `CMakePresets.json`.

### Testing

```bash
# Test every module
srlow test all

# Test specific modules
srlow test target module1 module2
```

Runs `ceedling test:all gcov:all valgrind:all` in each module's `Test/` directory.

### Analysing

```bash
# Analyse every module
srlow analyse all --preset [Debug | Release]

# Analyse specific modules
srlow analyse target module1 module2 --preset debug
```

Runs Scitools Understand `codecheck` (MISRA-C 2025) on each module. Requires `und` to be installed and licensed, a `misra-c2025.json` config file at the repo root, and a `compile_commands.json`. This can easily be gotten by running srlow build.

A module is analysed only if it has an `analyse.txt` file at its top level, listing which files to check. All paths are relative to module root:

```
# This is a comment
Core/Src/my_math.c
Core/Src/*.c # inline comments and glob patterns are both supported
Drivers/**/*.c
```

### Installing

All `srlow build...` commands also installs generated binaries into a repo root `deploy/module_name/` directory. In each STM32 projects' CMakeLists.txt, the following addition is required:

```bash
# Installing ===========================
# Auto-generate binary from ELF
add_custom_command(
TARGET ${CMAKE_PROJECT_NAME}
POST_BUILD
COMMAND ${CMAKE_OBJCOPY} -O binary $<TARGET_FILE:${CMAKE_PROJECT_NAME}> $<TARGET_FILE_DIR:${CMAKE_PROJECT_NAME}>/${CMAKE_PROJECT_NAME}.bin
)

install(
FILES
$<TARGET_FILE_DIR:${CMAKE_PROJECT_NAME}>/${CMAKE_PROJECT_NAME}.bin
$<TARGET_FILE:${CMAKE_PROJECT_NAME}>
DESTINATION ${CMAKE_PROJECT_NAME}
)
```

## Example workflow
SR-Gungnir and SR-Mjolnir:
```bash
cd path/to/your/project/src
srpkg create my_dds_node
Expand All @@ -131,7 +211,16 @@ srbuild target my_dds_node
# or
srbuild all
```

SR-Amsvartnir:
```bash
# create STM32 Cube MX project in src/
# add the install section to CMakeLists.txt
srlow build target my_project --preset Debug
# Create src/my_project/Test and initialise as a Ceedling project
srlow test target my_project
# Create analyse.txt file with target files, then run:
srlow analyse target my_project
```
## Contributors
- Ryan Wong || z5417983
- Henry Jiang || z5416365
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
[project]
name = "sr-dev-tools"
version = "0.1.0"
version = "3.0.0"
description = "Sunswift high-level DDS package and build tooling (srbuild, srpkg)"
requires-python = ">=3.11"
dependencies = []

[project.scripts]
srbuild = "sr_dev_tools.srbuild:main"
srpkg = "sr_dev_tools.srpkg:main"
srlow = "sr_dev_tools.srlow:main"

[build-system]
requires = ["hatchling"]
Expand Down
28 changes: 27 additions & 1 deletion src/sr_dev_tools/common_helpers.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,35 @@
"""Common utilities shared by srbuild/srpkg."""

from typing import NoReturn
from pathlib import Path
import logging
import sys

def setup_logging(name: str) -> logging.Logger:
"""Configure root logging and return the top-level logger for 'name'."""
logging.basicConfig(format="[%(levelname)s] [%(name)s] %(message)s", level=logging.INFO)
return logging.getLogger(name)

def find_repo_root(cwd: Path, marker_file: str) -> Path:
"""Walk up from `cwd` looking for an empty marker_file.
Returns the directory containing the marker. Dies if no marker is found in
`cwd` or any parent directory.
"""
candidate = cwd.resolve()
for directory in (candidate, *candidate.parents):
if (directory / marker_file).exists():
return directory
die(
f"No {marker_file} marker found in '{cwd}' or any parent directory.\n"
f"This script requires a {marker_file} marker file at the root of your project "
)

def die(msg: str) -> NoReturn:
print(msg)
logging.getLogger().error(msg)
sys.exit(1)

def print_box(text: str, width: int = 60, ch: str = "-") -> None:
"""Print `text` centred inside a bordered box `width` characters wide."""
print(ch * width)
print(f"{text}".center(width))
print(ch * width)
40 changes: 14 additions & 26 deletions src/sr_dev_tools/srbuild.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
from typing import Optional
from pathlib import Path
from dataclasses import dataclass
from sr_dev_tools.common_helpers import die
from sr_dev_tools.common_helpers import die, find_repo_root, print_box

# =================================================================================================
# CONSTANTS
Expand All @@ -44,23 +44,6 @@ class BuildData:
# HELPERS
# =================================================================================================

def find_repo_root(cwd: Path) -> Path:
"""Walk up from `cwd` looking for an empty MARKER_FILE, like git looks for .git.

Returns the directory containing the marker. Dies if no marker is found in
`cwd` or any parent directory.
"""
candidate = cwd.resolve()
for directory in (candidate, *candidate.parents):
if (directory / MARKER_FILE).exists():
return directory
die(
f"[srbuild] No {MARKER_FILE} marker found in '{cwd}' or any parent directory.\n"
f"[srbuild] srbuild requires a {MARKER_FILE} marker file at the root of your project "
f"(alongside CMakeLists.txt)."
)


def safe_rmdir(path: Path, build_root: Path) -> bool:
"""Absolutely every error check again just to confirm before deletion.
In case any bugs in error checking happen before. Then deletes specified dir.
Expand Down Expand Up @@ -103,7 +86,7 @@ def configure_cmake(build_data: BuildData) -> None:
build_data (BuildData)
"""

print("============ CMake Initialisation ============")
print_box("CMake Initialisation", width=60, ch="=")
print(f"[srbuild] TARGET PLATFORM: {build_data.target_platform}")
# CMakeLists.txt at build root MUST exist. Makes build dir if does not exist
if not (build_data.cmakelists_path.exists() and build_data.cmakelists_path.is_file()):
Expand Down Expand Up @@ -146,7 +129,8 @@ def build(targets: Optional[list[str]], build_data: BuildData, jobs: int) -> Non
build_data (BuildData)
jobs (int): number of jobs to run in parallel
"""
print("============= Building Targets ===============")
print()
print_box("Building Targets", width=60, ch="=")
jobs_str = f"{jobs}"
start_time = time.time()
# Build targets
Expand All @@ -167,7 +151,8 @@ def build(targets: Optional[list[str]], build_data: BuildData, jobs: int) -> Non
except subprocess.CalledProcessError:
die("[srbuild] Build: Error building targets")

print("=============== Build Complete =================")
print()
print_box("Build Complete", width=60, ch="=")
end_time = time.time()
print(f"[srbuild] Build finished in {end_time-start_time:.4f} seconds")

Expand All @@ -178,7 +163,8 @@ def install(targets: Optional[list[str]], build_data: BuildData) -> None:
targets (Optional[list[str]]): none if all targets, else list of targets
build_data (BuildData):
"""
print("============= Installing Targets ===============")
print()
print_box("Installing Targets", width=60, ch="=")
start_time = time.time()
# Install targets
try:
Expand All @@ -202,13 +188,14 @@ def install(targets: Optional[list[str]], build_data: BuildData) -> None:
die("[srbuild] Build: Error installing targets")


print("=============== Install Complete =================")
print()
print_box("Install Complete", width=60, ch="=")
end_time = time.time()
print(f"[srbuild] Install finished in {end_time-start_time:.4f} seconds")

def clean(build_data: BuildData) -> None:
"""Deletes the entire build/ directory under build_data.build_root."""
print("============= Cleaning Targets ===============")
print_box("Cleaning Targets", width=60, ch="=")
path = build_data.build_root / "build"
res = input(f"[srbuild] Would you like to remove {path}? (y/n): ")
if res.lower() != "y":
Expand All @@ -222,7 +209,8 @@ def clean(build_data: BuildData) -> None:
else:
print(f"[srbuild] Clean: {path} does not exist")

print("============== Clean Complete ================")
print()
print_box("Clean Complete", width=60, ch="=")

def build_all(jobs: int, build_data: BuildData) -> None:
"""Configure, build, and install all targets."""
Expand Down Expand Up @@ -262,7 +250,7 @@ def parse_args() -> argparse.Namespace:

def main():
# build_root: nearest ancestor of CWD containing a .sunswift-evsn marker (dies if none found)
build_root = find_repo_root(CWD)
build_root = find_repo_root(CWD, MARKER_FILE)
args = parse_args()

if getattr(args, "qnx", None):
Expand Down
Loading
Loading