From 56d649254d4153c3a01304b72b0a2270a18fe5d6 Mon Sep 17 00:00:00 2001 From: Ryan Date: Tue, 3 Mar 2026 21:17:45 +1100 Subject: [PATCH 1/9] feat: Removed specifying repo root option from srbuild. Always takes git repo root and validates it. Reverted srpkg for future updates --- host/srbuild | 24 ++---- host/srpkg | 214 ++++++++++++++------------------------------------- 2 files changed, 63 insertions(+), 175 deletions(-) diff --git a/host/srbuild b/host/srbuild index ed7163d..b1742db 100755 --- a/host/srbuild +++ b/host/srbuild @@ -6,7 +6,7 @@ # Date: 7/01/2026 # Author: Ryan Wong # -# Wrapper around CMake to allow you to intuitevly build and install all, one, or some targets +# Wrapper around CMake to allow you to intuitively build and install all, one, or some targets # # # Usage: @@ -58,20 +58,10 @@ def git_toplevel(path: Path) -> Path: die("Error: git returned empty repository root") return Path(root).resolve() -def resolve_repo_root(cli_root: Optional[str]) -> Path: - env_root = os.environ.get("SR_REPO_ROOT") - if cli_root: - candidate = Path(cli_root).expanduser().resolve() - elif env_root: - candidate = Path(env_root).expanduser().resolve() - else: - return git_toplevel(CWD) - +def resolve_repo_root() -> Path: + candidate = git_toplevel(CWD) if not candidate.exists() or not candidate.is_dir(): die("Error: repo root does not exist or is not a directory") - git_root = git_toplevel(candidate) - if git_root != candidate: - die("Error: repo root is not a git repository root") return candidate def validate_repo_root(repo_root: Path) -> None: @@ -158,7 +148,7 @@ def build(targets: Optional[list[str]], jobs: int) -> None: if targets is None: print("Build: Building all targets") subprocess.run([ - "cmake", "--build", str(BUILD_DIR_PATH), "-j", jobs_str + "cmake", "--build", str(BUILD_DIR_PATH), "--", jobs_str ], check=True) else: print("Build: Building specified targets") @@ -246,10 +236,6 @@ def main(): common_flags = argparse.ArgumentParser(add_help=False) common_flags.add_argument("--jobs", "-j", default=8, type=int, help="Number of parallel jobs Make runs. Default=8") - parser.add_argument( - "--repo-root", - help="Path to repository root (or set SR_REPO_ROOT). Defaults to git root.", - ) level1_junction = parser.add_subparsers(dest="command", required=True) command_all = level1_junction.add_parser("all", parents=[common_flags], help="Build and install all targets") @@ -260,7 +246,7 @@ def main(): ### SANITY CHECK - immediately fail if not used within repository global REPO_ROOT, BUILD_DIR_PATH, CMAKELISTS_PATH, INSTALL_PREFIX - REPO_ROOT = resolve_repo_root(args.repo_root) + REPO_ROOT = resolve_repo_root() validate_repo_root(REPO_ROOT) BUILD_DIR_PATH = REPO_ROOT / "build" CMAKELISTS_PATH = REPO_ROOT / "CMakeLists.txt" diff --git a/host/srpkg b/host/srpkg index 91c58e1..ca8fa37 100755 --- a/host/srpkg +++ b/host/srpkg @@ -8,35 +8,31 @@ # # Creates a new package according to this structure in the directory which you # run this script from -# +# # / # .srpkg # src/ # include/ -# config/ +# param/ # CMakeLists.txt # README.md -# param/ (optional, only when --with-param) # # src -> all your .cpp files # include -> all your .hpp files -# config -> build config for OS targets -# param -> json files (probably) for static params (optional) -# +# param -> json files (probably) for static params +# # Usage in directory you want to create package in: -# - srpkg create [--all|--linux|--qnx] [--with-param] +# - srpkg create # Usage from anywhere in repository # - srpkg info # - srpkg list ############################################################################### import argparse -import os import sys import re import shutil import json -import subprocess from typing import Optional from datetime import datetime from pathlib import Path @@ -47,24 +43,24 @@ from dataclasses import dataclass # ================================================================================================= CWD = Path.cwd().resolve() -REPO_ROOT = Path() -MARKER_FILE = ".sunswift-evsn" +# THIS ASSUMES that repo root is 2 directories above this script... +REPO_ROOT = Path(__file__).resolve().parents[2] +if not (REPO_ROOT/"src").exists() and not (REPO_ROOT/"core").exists(): + print(f"Error: {__file__} not 2 below repo root") # All of these are relative to pkg top level NESTED_DIRS = { "src": "src", "include": "include", - "config": "config", + "param": "param", } FILES = { "metadata": ".srpkg", "make": "CMakeLists.txt", "readme": "README.md", - "config": "config/config.json", - "main": "src/main.cpp", + "param": "param/{pkg_name}_param.json", + "main": "src/main.cpp" } -PARAM_DIR = "param" -PARAM_FILE = "param/{pkg_name}_param.json" CLI_ARGS = { "create": ["name"], @@ -84,47 +80,7 @@ class PkgPaths: def die(msg: str) -> None: print(msg) sys.exit(1) - -def git_toplevel(path: Path) -> Path: - try: - result = subprocess.run( - ["git", "-C", str(path), "rev-parse", "--show-toplevel"], - check=True, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - text=True, - ) - except FileNotFoundError: - die("Error: git not found; cannot determine repository root") - except subprocess.CalledProcessError: - die("Error: not a git repository (or any of the parent directories)") - root = result.stdout.strip() - if not root: - die("Error: git returned empty repository root") - return Path(root).resolve() - -def resolve_repo_root(cli_root: Optional[str]) -> Path: - env_root = os.environ.get("SR_REPO_ROOT") - if cli_root: - candidate = Path(cli_root).expanduser().resolve() - elif env_root: - candidate = Path(env_root).expanduser().resolve() - else: - return git_toplevel(CWD) - - if not candidate.exists() or not candidate.is_dir(): - die("Error: repo root does not exist or is not a directory") - git_root = git_toplevel(candidate) - if git_root != candidate: - die("Error: repo root is not a git repository root") - return candidate - -def validate_repo_root(repo_root: Path) -> None: - if not (repo_root / "src").exists() and not (repo_root / "core").exists(): - die("Error: repository root missing src/ or core/") - if not (repo_root / MARKER_FILE).exists(): - die(f"Error: marker file '{MARKER_FILE}' not found in repository root") - + def dir_size(path: Path) -> int: return sum( p.stat().st_size @@ -151,26 +107,23 @@ Topic | C++ Type | Description /domain/subsystem/topic|`C++ Type`|BMS Voltage ## Parameters -Add runtime parameters here if you created a param JSON file. - -## Build Config -See `config/config.json` for the OS build flags. +Under construction! ## Contributors Written by `Your name here` | `Your zID here`""" with (paths.abs_pkg_path / FILES["readme"]).open("w") as file: file.write(text) - -def fill_cmakelists(paths: PkgPaths, create_param: bool) -> None: + +def fill_cmakelists(paths: PkgPaths) -> None: text = f"""# Per-node info set(TARGET_NAME {paths.pkg_name}) -set(SOURCES +set(SOURCES src/main.cpp ) set(INCLUDE_DIRS include ) -set (LIBS +set (LIBS dds_node # add your type libraries here # add other dependencies here @@ -186,9 +139,7 @@ install( TARGETS ${{TARGET_NAME}} RUNTIME DESTINATION bin COMPONENT ${{TARGET_NAME}} -)""" - if create_param: - text += f""" +) install( FILES param/${{TARGET_NAME}}_param.json DESTINATION param @@ -197,16 +148,9 @@ install( with (paths.abs_pkg_path / FILES["make"]).open("w") as file: file.write(text) -def fill_config(paths: PkgPaths, build_config: dict[str, bool]) -> None: - config_path = paths.abs_pkg_path / FILES["config"] - with config_path.open("w") as file: - json.dump(build_config, file, indent=2, sort_keys=True) - file.write("\n") - +# TODO: When we figure out static params def fill_param(paths: PkgPaths) -> None: - param_path = paths.abs_pkg_path / PARAM_FILE.format(pkg_name=paths.pkg_name) - with param_path.open("w") as json_file: - json_file.write("{}") + pass def pkg_exist_elsewhere(paths: PkgPaths) -> tuple[bool, Optional[Path]]: """Recursively checks if valid package with the same name exists in src @@ -215,7 +159,7 @@ def pkg_exist_elsewhere(paths: PkgPaths) -> tuple[bool, Optional[Path]]: Returns: tuple[bool, Optional[Path]]: (True/False, RELATIVE path of where it is/None) """ - + src_path = REPO_ROOT / "src" for path in src_path.rglob(paths.pkg_name): if path.is_dir() and (path/FILES["metadata"]).exists(): @@ -224,31 +168,31 @@ def pkg_exist_elsewhere(paths: PkgPaths) -> tuple[bool, Optional[Path]]: return (False, None) def safe_rmdir(paths: PkgPaths) -> None: - """Absolutely every error check again just to confirm before deletion. + """Absolutely every error check again just to confirm before deletion. In case any bugs in error checking happen before. Then deletes Args: paths (PkgPaths): dataclass which stores name and abs path of pkg """ if not paths.abs_pkg_path.exists(): die("Delete: Path does not exist") - + if not paths.abs_pkg_path.is_dir(): die("Delete: Path is not a directory") - + if not (paths.abs_pkg_path/FILES["metadata"]).exists(): die("Delete: Path is not a Sunswift Package") - + if paths.abs_pkg_path.is_symlink(): die("Delete: Path is a symlink") try: - # Check if path is within repository + # Check if path is within repository paths.abs_pkg_path.relative_to(REPO_ROOT) except ValueError: die("Path not within SRP8-130_EMBD_High_Level repository") - - if (paths.abs_pkg_path == REPO_ROOT or - paths.abs_pkg_path == "/" or + + if (paths.abs_pkg_path == REPO_ROOT or + paths.abs_pkg_path == "/" or paths.abs_pkg_path == REPO_ROOT / "src"): die("wtf are u doing man") @@ -271,9 +215,9 @@ def validate_name(name: str) -> PkgPaths: def parse_args() ->argparse.Namespace: """ - Constructs a 1 level parser with all arguments specified in CLI_ARGS above, + Constructs a 1 level parser with all arguments specified in CLI_ARGS above, then executes and returns args - + You can use args.command (first level), then args.name for leaf arg """ ### Command line arguments @@ -281,10 +225,6 @@ def parse_args() ->argparse.Namespace: description=f"Sunswift DDS package management tool. \ Packages are created in your current working directory." ) - root_parser.add_argument( - "--repo-root", - help="Path to repository root (or set SR_REPO_ROOT). Defaults to git root.", - ) # at a junction, there can be many options, where one option can be another junction # imagine a tree structure level1_junction = root_parser.add_subparsers(dest="command", required=True) @@ -292,55 +232,24 @@ def parse_args() ->argparse.Namespace: level1_option = level1_junction.add_parser(command) for arg in arg_array: level1_option.add_argument(arg) - if command == "create": - build_group = level1_option.add_mutually_exclusive_group() - build_group.add_argument( - "--all", - action="store_true", - help="Enable linux and qnx builds", - ) - build_group.add_argument( - "--linux", - action="store_true", - help="Enable linux build only", - ) - build_group.add_argument( - "--qnx", - action="store_true", - help="Enable qnx build only", - ) - level1_option.add_argument( - "--with-param", - action="store_true", - help="Create an optional param JSON file", - ) - + return root_parser.parse_args() -def mkdir_package(paths: PkgPaths, create_param: bool) -> tuple[list[str], list[str]]: +def mkdir_package(paths: PkgPaths) -> None: """Actually makes the directory structure""" - created_dirs = [] - created_files = [] paths.abs_pkg_path.mkdir() for dir in NESTED_DIRS.values(): (paths.abs_pkg_path / dir).mkdir() - created_dirs.append(dir) - if create_param: - (paths.abs_pkg_path / PARAM_DIR).mkdir() - created_dirs.append(PARAM_DIR) - for file in FILES.values(): - new_file = file.format(pkg_name=paths.pkg_name) + new_file = file + if "{pkg_name}" in file: + new_file = file.format(pkg_name=paths.pkg_name) (paths.abs_pkg_path / new_file).touch() - created_files.append(new_file) - if create_param: - param_file = PARAM_FILE.format(pkg_name=paths.pkg_name) - (paths.abs_pkg_path / param_file).touch() - created_files.append(param_file) + if "json" in new_file: + with (paths.abs_pkg_path / new_file).open("w") as json_file: + json_file.write("{}") - return created_dirs, created_files - -def pkg_create(paths: PkgPaths, build_config: dict[str, bool], create_param: bool) -> None: +def pkg_create(paths: PkgPaths) -> None: """Creates directory based on structure in top comment if it doesn't already exist. Args: paths (PkgPaths): dataclass which stores name and abs path of pkg @@ -360,14 +269,12 @@ def pkg_create(paths: PkgPaths, build_config: dict[str, bool], create_param: boo if res: die(f"Error: {paths.pkg_name} already exists at '{location}'") - # Create directories and files + # Create directories and files try: - created_dirs, created_files = mkdir_package(paths, create_param) + mkdir_package(paths) fill_readme(paths) - fill_cmakelists(paths, create_param) - fill_config(paths, build_config) - if create_param: - fill_param(paths) + fill_cmakelists(paths) + fill_param(paths) except Exception as e: if paths.abs_pkg_path.exists(): safe_rmdir(paths) @@ -378,11 +285,16 @@ def pkg_create(paths: PkgPaths, build_config: dict[str, bool], create_param: boo print("Created structure:") # Directories - for dir in created_dirs: + for dir in NESTED_DIRS.values(): + dir_path = paths.abs_pkg_path / dir print(f" {dir}") # Files - for file_name in created_files: - print(f" {file_name}") + for file_name in FILES.values(): + new_file_name = file_name + if "{pkg_name}" in file_name: + new_file_name = file_name.format(pkg_name=paths.pkg_name) + file_path = paths.abs_pkg_path / new_file_name + print(f" {new_file_name}") def pkg_info(paths: PkgPaths) -> None: """ Finds package with given name in repository @@ -416,7 +328,7 @@ def pkg_info(paths: PkgPaths) -> None: # Combine all names to compute max padding all_names = list(NESTED_DIRS.values()) + list(FILES.values()) - max_len = max(len(name) for name in all_names) + 2 + max_len = max(len(name) for name in all_names) + 2 print(f"Package: '{paths.pkg_name}'") print(f"Location: {location}") @@ -424,7 +336,7 @@ def pkg_info(paths: PkgPaths) -> None: print(f"Contents: {num_dirs} directories, {num_files} files") print(f"Created: {creation_date}") print(f"Last modified: {last_mod_date}") - + # List source files src_dir = abs_path / "src" if src_dir.exists(): @@ -481,10 +393,7 @@ def main(): args = parse_args() ### SANITY CHECK: - # Check that script is run in repo - global REPO_ROOT - REPO_ROOT = resolve_repo_root(args.repo_root) - validate_repo_root(REPO_ROOT) + # Wheck that script is run in repo try: CWD.relative_to(REPO_ROOT) except ValueError: @@ -494,14 +403,7 @@ def main(): cmd = args.command if cmd == "create": paths = validate_name(args.name) - build_config = {"linux": True, "qnx": False} - if args.all: - build_config = {"linux": True, "qnx": True} - elif args.qnx: - build_config = {"linux": False, "qnx": True} - elif args.linux: - build_config = {"linux": True, "qnx": False} - pkg_create(paths, build_config, args.with_param) + pkg_create(paths) elif cmd == "info": paths = validate_name(args.name) pkg_info(paths) @@ -510,4 +412,4 @@ def main(): if __name__ == "__main__": - main() + main() \ No newline at end of file From 637438caad3024c0bd738afbe2d00e6711bb0f13 Mon Sep 17 00:00:00 2001 From: Ryan Date: Tue, 3 Mar 2026 21:25:46 +1100 Subject: [PATCH 2/9] fix: reverted srlaunch --- target/srlaunch | 69 ++++++------------------------------------------- 1 file changed, 8 insertions(+), 61 deletions(-) diff --git a/target/srlaunch b/target/srlaunch index cc4c81d..9e656a4 100755 --- a/target/srlaunch +++ b/target/srlaunch @@ -20,7 +20,7 @@ # - ./srlaunch target node1 node2 ############################################################################### -import os +import json import subprocess import sys import signal @@ -29,11 +29,12 @@ from argparse import ArgumentParser from pathlib import Path from typing import Dict, Optional -CWD = Path.cwd().resolve() -REPO_ROOT = Path() -DEPLOY_ROOT = Path() -BIN_PATH = Path() -MARKER_FILE = ".sunswift-evsn" +# This assumes srlaunch.py is in deploy/tools/ (it needs to know where bin is) +DEPLOY_ROOT = Path(__file__).resolve().parents[1] +BIN_PATH = DEPLOY_ROOT/"bin" + +if not (DEPLOY_ROOT/"bin").exists() and not (DEPLOY_ROOT/"param").exists(): + print(f"Error: {__file__} not in deploy/tools directory") # Store dict of processes where key is node name as str, value is process handle # also store dict of state @@ -53,46 +54,6 @@ def log(msg: str, level: str): """Prints log formatted""" print(f"[srlaunch] [{level}] {msg}", flush=True) -def git_toplevel(path: Path) -> Path: - try: - result = subprocess.run( - ["git", "-C", str(path), "rev-parse", "--show-toplevel"], - check=True, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - text=True, - ) - except FileNotFoundError: - die("git not found; cannot determine repository root") - except subprocess.CalledProcessError: - die("not a git repository (or any of the parent directories)") - root = result.stdout.strip() - if not root: - die("git returned empty repository root") - return Path(root).resolve() - -def resolve_repo_root(cli_root: Optional[str]) -> Path: - env_root = os.environ.get("SR_REPO_ROOT") - if cli_root: - candidate = Path(cli_root).expanduser().resolve() - elif env_root: - candidate = Path(env_root).expanduser().resolve() - else: - return git_toplevel(CWD) - - if not candidate.exists() or not candidate.is_dir(): - die("repo root does not exist or is not a directory") - git_root = git_toplevel(candidate) - if git_root != candidate: - die("repo root is not a git repository root") - return candidate - -def validate_repo_root(repo_root: Path) -> None: - if not (repo_root / "src").exists() and not (repo_root / "core").exists(): - die("repository root missing src/ or core/") - if not (repo_root / MARKER_FILE).exists(): - die(f"marker file '{MARKER_FILE}' not found in repository root") - # ================================================================================================= # CORE LOGIC # ================================================================================================= @@ -217,26 +178,12 @@ signal.signal(signal.SIGTERM, shutdown_handler) def main(): parser = ArgumentParser() - parser.add_argument( - "--repo-root", - help="Path to repository root (or set SR_REPO_ROOT). Defaults to git root.", - ) level1_junction = parser.add_subparsers(dest="command", required=True) command_all = level1_junction.add_parser("all", help="Launch all nodes in deploy/bin") command_target = level1_junction.add_parser("target", help="Launch specified nodes in deploy/bin") command_target.add_argument("targets", nargs="+", help="One or more binary node names") args = parser.parse_args() - - global REPO_ROOT, DEPLOY_ROOT, BIN_PATH - REPO_ROOT = resolve_repo_root(args.repo_root) - validate_repo_root(REPO_ROOT) - DEPLOY_ROOT = REPO_ROOT / "deploy" - BIN_PATH = DEPLOY_ROOT / "bin" - try: - CWD.relative_to(REPO_ROOT) - except ValueError: - die("script must be run within repository") if args.command == "all": launch(None) @@ -250,4 +197,4 @@ def main(): time.sleep(0.1) if __name__ == "__main__": - main() + main() \ No newline at end of file From 735e4c2c58f07f686be93bbeb01333fa41816c0f Mon Sep 17 00:00:00 2001 From: Ryan Date: Tue, 3 Mar 2026 21:28:36 +1100 Subject: [PATCH 3/9] fix: updated default readme and cmakelists in srpkg --- host/srpkg | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/host/srpkg b/host/srpkg index ca8fa37..5ee86fd 100755 --- a/host/srpkg +++ b/host/srpkg @@ -96,15 +96,15 @@ Briefly describe the purpose of this DDS node. ## Topics Published to Enter topics published to below -Topic | C++ Type | Description +Topic | Topic Type | Description ------|----------|------------ -/domain/subsystem/topic|`C++ Type`|BMS Voltage +/domain/subsystem/topic|`Topic Type`|BMS Voltage ## Topics Subscribed to Enter topics subscribed to below -Topic | C++ Type | Description +Topic | Topic Type | Description ------|----------|------------ -/domain/subsystem/topic|`C++ Type`|BMS Voltage +/domain/subsystem/topic|`Topic Type`|BMS Voltage ## Parameters Under construction! @@ -124,7 +124,7 @@ set(INCLUDE_DIRS include ) set (LIBS - dds_node + sr_node # add your type libraries here # add other dependencies here ) From 2a80f80c6dbb623256b9b4a987dace4c956b8eb5 Mon Sep 17 00:00:00 2001 From: Ryan Date: Mon, 16 Mar 2026 21:27:13 +1100 Subject: [PATCH 4/9] feat: refactored dev tools to put Henry's new repo_root finding functions in srutils. Added .gitignore --- .gitignore | 62 +++++++++++++++++++++++++++++++++++++++++++++++++ host/srbuild | 50 ++++++++------------------------------- host/srpkg | 19 ++++++++------- host/srutils.py | 42 +++++++++++++++++++++++++++++++++ 4 files changed, 124 insertions(+), 49 deletions(-) create mode 100644 .gitignore create mode 100644 host/srutils.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1f3fd59 --- /dev/null +++ b/.gitignore @@ -0,0 +1,62 @@ +# Python +__pycache__/ +*.py[cod] +*$py.class +*.pyo +*.pyd + +# Distribution / packaging +dist/ +build/ +*.egg-info/ +*.egg +.eggs/ +wheels/ + +# Virtual environments +.venv/ +venv/ +env/ +ENV/ + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage +.tox/ +.nox/ +.coverage +.coverage.* +coverage.xml +*.cover +htmlcov/ +.pytest_cache/ +.hypothesis/ + +# Type checking +.mypy_cache/ +.dmypy.json +.pyre/ +.pytype/ + +# CLI specific +*.log +*.pid + +# Environment / secrets +.env +.env.* +*.env +!.env.example + +# IDEs +.idea/ +.vscode/ +*.swp +*.swo +*~ + +# OS +.DS_Store +Thumbs.db \ No newline at end of file diff --git a/host/srbuild b/host/srbuild index b1742db..7edda08 100755 --- a/host/srbuild +++ b/host/srbuild @@ -2,7 +2,7 @@ ############################################################################### # Sunswift High Level build tool -# Version: V2.0 +# Version: V2.1 # Date: 7/01/2026 # Author: Ryan Wong # @@ -16,60 +16,24 @@ ############################################################################### import argparse -import os import sys import subprocess import time import shutil from typing import Optional from pathlib import Path +from srutils import resolve_repo_root, validate_repo_root, die CWD = Path.cwd().resolve() REPO_ROOT = Path() BUILD_DIR_PATH = Path() CMAKELISTS_PATH = Path() INSTALL_PREFIX = Path() -MARKER_FILE = ".sunswift-evsn" # ================================================================================================= # HELPERS # ================================================================================================= -def die(msg: str) -> None: - """Kills script and prints out msg""" - print(msg) - sys.exit(1) - -def git_toplevel(path: Path) -> Path: - try: - result = subprocess.run( - ["git", "-C", str(path), "rev-parse", "--show-toplevel"], - check=True, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - text=True, - ) - except FileNotFoundError: - die("Error: git not found; cannot determine repository root") - except subprocess.CalledProcessError: - die("Error: not a git repository (or any of the parent directories)") - root = result.stdout.strip() - if not root: - die("Error: git returned empty repository root") - return Path(root).resolve() - -def resolve_repo_root() -> Path: - candidate = git_toplevel(CWD) - if not candidate.exists() or not candidate.is_dir(): - die("Error: repo root does not exist or is not a directory") - return candidate - -def validate_repo_root(repo_root: Path) -> None: - if not (repo_root / "src").exists() and not (repo_root / "core").exists(): - die("Error: repository root missing src/ or core/") - if not (repo_root / MARKER_FILE).exists(): - die(f"Error: marker file '{MARKER_FILE}' not found in repository root") - def safe_rmdir(path: 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 @@ -246,8 +210,14 @@ def main(): ### SANITY CHECK - immediately fail if not used within repository global REPO_ROOT, BUILD_DIR_PATH, CMAKELISTS_PATH, INSTALL_PREFIX - REPO_ROOT = resolve_repo_root() - validate_repo_root(REPO_ROOT) + try: + REPO_ROOT = resolve_repo_root(CWD) + except Exception as exc: + die(str(exc)) + try: + validate_repo_root(REPO_ROOT) + except Exception as exc: + die(str(exc)) BUILD_DIR_PATH = REPO_ROOT / "build" CMAKELISTS_PATH = REPO_ROOT / "CMakeLists.txt" INSTALL_PREFIX = REPO_ROOT / "deploy" diff --git a/host/srpkg b/host/srpkg index 5ee86fd..29dbdff 100755 --- a/host/srpkg +++ b/host/srpkg @@ -2,7 +2,7 @@ ############################################################################### # Sunswift High Level DDS Package generator -# Version: V3.0 +# Version: V3.1 # Date: 24/12/2025 # Author: Ryan Wong # @@ -37,16 +37,14 @@ from typing import Optional from datetime import datetime from pathlib import Path from dataclasses import dataclass +from srutils import resolve_repo_root, validate_repo_root, die, MARKER_FILE # ================================================================================================= # CONSTANTS # ================================================================================================= CWD = Path.cwd().resolve() -# THIS ASSUMES that repo root is 2 directories above this script... -REPO_ROOT = Path(__file__).resolve().parents[2] -if not (REPO_ROOT/"src").exists() and not (REPO_ROOT/"core").exists(): - print(f"Error: {__file__} not 2 below repo root") +REPO_ROOT = Path() # All of these are relative to pkg top level NESTED_DIRS = { @@ -77,10 +75,6 @@ class PkgPaths: # HELPERS # ================================================================================================= -def die(msg: str) -> None: - print(msg) - sys.exit(1) - def dir_size(path: Path) -> int: return sum( p.stat().st_size @@ -393,6 +387,13 @@ def main(): args = parse_args() ### SANITY CHECK: + global REPO_ROOT + try: + REPO_ROOT = resolve_repo_root(CWD) + validate_repo_root(REPO_ROOT) + except Exception as exc: + die(str(exc)) + # Wheck that script is run in repo try: CWD.relative_to(REPO_ROOT) diff --git a/host/srutils.py b/host/srutils.py new file mode 100644 index 0000000..18a0412 --- /dev/null +++ b/host/srutils.py @@ -0,0 +1,42 @@ +"""Common repo utilities for srbuild/srpkg.""" + +from pathlib import Path +import subprocess +import sys + +MARKER_FILE = ".sunswift-evsn" + +def die(msg: str) -> None: + print(msg) + sys.exit(1) + +def git_toplevel(path: Path) -> Path: + try: + result = subprocess.run( + ["git", "-C", str(path), "rev-parse", "--show-toplevel"], + check=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + ) + except FileNotFoundError: + raise RuntimeError("Error: git not found; cannot determine repository root") + except subprocess.CalledProcessError: + raise RuntimeError("Error: not a git repository (or any of the parent directories)") + + root = result.stdout.strip() + if not root: + raise RuntimeError("Error: git returned empty repository root") + return Path(root).resolve() + +def resolve_repo_root(cwd: Path = Path.cwd()) -> Path: + candidate = git_toplevel(cwd) + if not candidate.exists() or not candidate.is_dir(): + raise RuntimeError("Error: repo root does not exist or is not a directory") + return candidate + +def validate_repo_root(repo_root: Path, marker_file: str = MARKER_FILE) -> None: + if not ((repo_root / "src").exists() and (repo_root / "core").exists()): + raise RuntimeError("Error: repository root missing src/ or core/\nYou may be running this in a submodule") + if not (repo_root / marker_file).exists(): + raise RuntimeError(f"Error: marker file '{marker_file}' not found in repository root") From 7a424eb2b4723810780f86fe276e55659ea9eaa9 Mon Sep 17 00:00:00 2001 From: Ryan Date: Sat, 21 Mar 2026 15:09:41 +1100 Subject: [PATCH 5/9] feat: updated srpkg to autogenerate toml param file instead of json + add autofill main file --- host/srpkg | 46 ++++++++++++++++++++++++++++++++-------------- 1 file changed, 32 insertions(+), 14 deletions(-) diff --git a/host/srpkg b/host/srpkg index 29dbdff..c49d5a4 100755 --- a/host/srpkg +++ b/host/srpkg @@ -19,7 +19,7 @@ # # src -> all your .cpp files # include -> all your .hpp files -# param -> json files (probably) for static params +# param -> files for static params # # Usage in directory you want to create package in: # - srpkg create @@ -29,15 +29,13 @@ ############################################################################### import argparse -import sys import re import shutil -import json from typing import Optional from datetime import datetime from pathlib import Path from dataclasses import dataclass -from srutils import resolve_repo_root, validate_repo_root, die, MARKER_FILE +from srutils import resolve_repo_root, validate_repo_root, die # ================================================================================================= # CONSTANTS @@ -56,7 +54,7 @@ FILES = { "metadata": ".srpkg", "make": "CMakeLists.txt", "readme": "README.md", - "param": "param/{pkg_name}_param.json", + "param": "param/{pkg_name}_param.toml", "main": "src/main.cpp" } @@ -135,16 +133,39 @@ install( COMPONENT ${{TARGET_NAME}} ) install( - FILES param/${{TARGET_NAME}}_param.json + FILES param/${{TARGET_NAME}}_param.toml DESTINATION param COMPONENT ${{TARGET_NAME}} )""" with (paths.abs_pkg_path / FILES["make"]).open("w") as file: file.write(text) -# TODO: When we figure out static params def fill_param(paths: PkgPaths) -> None: - pass + text = f"""# {paths.pkg_name}_param.toml +# Parameter file for {paths.pkg_name} +# All parameters are optional. Missing keys fall back to declared defaults in the node. +# Undeclared parameters are ignored + +[params] +# example_string = "hello" +# example_int = 42 +# example_float = 3.14 +# example_bool = true + +""" + with (paths.abs_pkg_path / FILES["param"]).open("w") as file: + file.write(text) + +def fill_main(paths: PkgPaths) -> None: + text = f"""#include "my_node.hpp" // TODO: change to your node + +int main(int argc, char* argv[]) {{ + // TODO: initialise SRNode + // TODO: Spin SRNode and catch exceptions + return 0; +}} + +""" def pkg_exist_elsewhere(paths: PkgPaths) -> tuple[bool, Optional[Path]]: """Recursively checks if valid package with the same name exists in src @@ -153,7 +174,6 @@ def pkg_exist_elsewhere(paths: PkgPaths) -> tuple[bool, Optional[Path]]: Returns: tuple[bool, Optional[Path]]: (True/False, RELATIVE path of where it is/None) """ - src_path = REPO_ROOT / "src" for path in src_path.rglob(paths.pkg_name): if path.is_dir() and (path/FILES["metadata"]).exists(): @@ -194,7 +214,7 @@ def safe_rmdir(paths: PkgPaths) -> None: def validate_name(name: str) -> PkgPaths: ### Validate node package name - pattern = r"^[a-z0-9_]*$" + pattern = r"^[a-z0-9_]*$" # technically not correct but... if not re.match(pattern, name): die("Invalid package name: must be in 'snake_case'") @@ -239,9 +259,6 @@ def mkdir_package(paths: PkgPaths) -> None: if "{pkg_name}" in file: new_file = file.format(pkg_name=paths.pkg_name) (paths.abs_pkg_path / new_file).touch() - if "json" in new_file: - with (paths.abs_pkg_path / new_file).open("w") as json_file: - json_file.write("{}") def pkg_create(paths: PkgPaths) -> None: """Creates directory based on structure in top comment if it doesn't already exist. @@ -269,6 +286,7 @@ def pkg_create(paths: PkgPaths) -> None: fill_readme(paths) fill_cmakelists(paths) fill_param(paths) + fill_main(paths) except Exception as e: if paths.abs_pkg_path.exists(): safe_rmdir(paths) @@ -394,7 +412,7 @@ def main(): except Exception as exc: die(str(exc)) - # Wheck that script is run in repo + # Check that script is run in repo try: CWD.relative_to(REPO_ROOT) except ValueError: From 4a0aec5334bc7ad859fe2709ccbfdaf6852bbc07 Mon Sep 17 00:00:00 2001 From: Ryan Date: Sat, 21 Mar 2026 16:32:31 +1100 Subject: [PATCH 6/9] feat: added integration testing for host cli tools --- .github/workflows/test.yml | 23 +++++ host/tests/conftest.py | 17 ++++ host/tests/test_srbuild_integration.py | 97 ++++++++++++++++++ host/tests/test_srpkg_integration.py | 134 +++++++++++++++++++++++++ 4 files changed, 271 insertions(+) create mode 100644 .github/workflows/test.yml create mode 100644 host/tests/conftest.py create mode 100644 host/tests/test_srbuild_integration.py create mode 100644 host/tests/test_srpkg_integration.py diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..877e108 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,23 @@ +# .github/workflows/test.yml +name: Tests + +on: + push: + pull_request: + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Install pytest + run: pip install pytest + + - name: Run tests + run: pytest host/tests/ -v \ No newline at end of file diff --git a/host/tests/conftest.py b/host/tests/conftest.py new file mode 100644 index 0000000..fe5e8aa --- /dev/null +++ b/host/tests/conftest.py @@ -0,0 +1,17 @@ +### This file contains pytest fixtures for the srpkg and srbuild tests. +import pytest +import subprocess +from pathlib import Path + +@pytest.fixture +def repo(tmp_path: Path) -> Path: + """Real git repo with the expected Sunswift structure.""" + subprocess.run(["git", "init"], cwd=tmp_path, check=True, capture_output=True) + (tmp_path / "src").mkdir() + (tmp_path / "core").mkdir() + (tmp_path / ".sunswift-evsn").touch() + return tmp_path + +@pytest.fixture +def src(repo: Path) -> Path: + return repo / "src" \ No newline at end of file diff --git a/host/tests/test_srbuild_integration.py b/host/tests/test_srbuild_integration.py new file mode 100644 index 0000000..7c749e5 --- /dev/null +++ b/host/tests/test_srbuild_integration.py @@ -0,0 +1,97 @@ +############################################################################### +# Integration tests for srbuild CLI +# Version: V1.0 +# Date: 21/03/2026 +# Author: Ryan Wong +# +# Made to run automatically on CI. Tests srbuild end to end by mocking a repository +# and running the CLI commands. Uses pytest. Assumes srbuild is one level above this +# Uses fixtures from conftest.py to set up a temporary git repository with the expected structure. +############################################################################### + +import pytest +import subprocess +from pathlib import Path + +# ================================================================================================= +# HELPER FUNCTIONS +# ================================================================================================= +def run(*args: str, cwd: Path) -> subprocess.CompletedProcess: + """Run srbuild CLI and returns CompletedProcess object.""" + srbuild_path = Path(__file__).resolve().parent.parent / "srbuild" + result = subprocess.run( + [str(srbuild_path), *args], + cwd=cwd, + capture_output=True, + text=True, + ) + return result + + +### srbuild sanity check tests ---------------------------------------------------------------- + +def test_rejects_outside_repo(repo: Path) -> None: + """Must fail when run outside a valid repository.""" + r = run("all", cwd=repo.parent) + assert r.returncode != 0 + +def test_rejects_missing_marker(repo: Path) -> None: + """Must fail when .sunswift-evsn marker is missing.""" + (repo / ".sunswift-evsn").unlink() + r = run("all", cwd=repo) + assert r.returncode != 0 + +def test_rejects_missing_src(repo: Path) -> None: + """Must fail when src/ is missing.""" + import shutil + shutil.rmtree(repo / "src") + r = run("all", cwd=repo) + assert r.returncode != 0 + +def test_rejects_missing_core(repo: Path) -> None: + """Must fail when core/ is missing.""" + import shutil + shutil.rmtree(repo / "core") + r = run("all", cwd=repo) + assert r.returncode != 0 + +def test_accepts_valid_repo(repo: Path) -> None: + """Should pass sanity checks and fail only on missing CMakeLists.txt, not repo structure.""" + r = run("all", cwd=repo) + # CMakeLists.txt doesn't exist so cmake will fail, but the repo check should pass + assert "not a git repository" not in r.stdout + assert "marker file" not in r.stdout + assert "missing src" not in r.stdout + +def test_rejects_no_cmake(repo: Path) -> None: + """Should fail when CMakeLists.txt is missing.""" + r = run("all", cwd=repo) + assert r.returncode != 0 + assert "CMakeLists.txt" in r.stdout + +def test_accepts_cmake(repo: Path) -> None: + """Should get past CMakeLists check when it exists.""" + (repo / "CMakeLists.txt").touch() + r = run("all", cwd=repo) + # Will fail because cmake isn't configured, but not because of our checks + assert "CMakeLists.txt does not exist" not in r.stdout + + +### srbuild target tests ---------------------------------------------------------------------- + +def test_target_requires_at_least_one(repo: Path) -> None: + """srbuild target with no targets should fail.""" + r = run("target", cwd=repo) + assert r.returncode != 0 + +def test_target_accepts_single(repo: Path) -> None: + """srbuild target with one target should pass sanity checks.""" + (repo / "CMakeLists.txt").touch() + r = run("target", "my_node", cwd=repo) + assert "CMakeLists.txt does not exist" not in r.stdout + +def test_target_accepts_multiple(repo: Path) -> None: + """srbuild target with multiple targets should pass sanity checks.""" + (repo / "CMakeLists.txt").touch() + r = run("target", "node_a", "node_b", cwd=repo) + assert "CMakeLists.txt does not exist" not in r.stdout \ No newline at end of file diff --git a/host/tests/test_srpkg_integration.py b/host/tests/test_srpkg_integration.py new file mode 100644 index 0000000..53e7d8d --- /dev/null +++ b/host/tests/test_srpkg_integration.py @@ -0,0 +1,134 @@ +############################################################################### +# Integration tests for srpkg CLI +# Version: V1.0 +# Date: 16/03/2026 +# Author: Ryan Wong +# +# Made to run automatically on CI. Tests srpkg end to end by mocking a repository +# and running the CLI commands. Uses pytest. Assumes srpkg is one level above this +# Uses fixtures from conftest.py to set up a temporary git repository with the expected structure. +############################################################################### + +import pytest +import subprocess +import os +from pathlib import Path + +# ================================================================================================= +# HELPER FUNCTIONS +# ================================================================================================= +def run(*args: str, cwd: Path) -> subprocess.CompletedProcess: + """Run srpkg CLI and returns CompletedProcess object.""" + env = os.environ.copy() + srpkg_path = Path(__file__).resolve().parent.parent / "srpkg" + result = subprocess.run( + [str(srpkg_path), *args], + cwd=cwd, + capture_output=True, + text=True, + env=env + ) + return result + + +# ================================================================================================= +# TESTS +# ================================================================================================= + +### srpkg create tests +def test_create_basic(repo: Path, src: Path) -> None: + """Create package in src called my_node and verify creation""" + r = run("create", "my_node", cwd=src) + assert r.returncode == 0 + + pkg = src / "my_node" + assert (pkg / ".srpkg").exists() + assert (pkg / "src").is_dir() + assert (pkg / "include").is_dir() + assert (pkg / "param").is_dir() + assert (pkg / "CMakeLists.txt").exists() + assert (pkg / "README.md").exists() + +def test_create_rejects_local_duplicate(repo, src): + run("create", "my_node", cwd=src) + r = run("create", "my_node", cwd=src) + assert r.returncode != 0 + assert "already exists" in r.stdout + +def test_create_rejects_duplicate_in_other_dir(repo, src): + sub = src / "subsystem" + sub.mkdir() + run("create", "my_node", cwd=sub) + + r = run("create", "my_node", cwd=src) + assert r.returncode != 0 + assert "already exists" in r.stdout + +def test_create_rejects_outside_src(repo): + r = run("create", "my_node", cwd=repo) + assert r.returncode != 0 + assert "src" in r.stdout + +def test_create_rejects_outside_repo(repo): + r = run("create", "my_node", cwd=repo.parent) + assert r.returncode != 0 + +def test_create_rejects_bad_name(repo, src): + for bad in ["MyNode", "my-node", "my node", "my.node"]: + r = run("create", bad, cwd=src) + assert r.returncode != 0, f"Expected failure for name: {bad}" + + +### srpkg info tests +def test_info_found(repo, src): + run("create", "my_node", cwd=src) + r = run("info", "my_node", cwd=src) + assert r.returncode == 0 + assert "my_node" in r.stdout + assert "Location" in r.stdout + +def test_info_not_found(repo, src): + r = run("info", "ghost_node", cwd=src) + assert r.returncode != 0 + +def test_info_shows_cpp_files(repo, src): + run("create", "my_node", cwd=src) + (src / "my_node" / "src" / "helper.cpp").write_text("// helper") + r = run("info", "my_node", cwd=src) + assert "helper.cpp" in r.stdout + +def test_info_shows_hpp_files(repo, src): + run("create", "my_node", cwd=src) + (src / "my_node" / "include" / "helper.hpp").write_text("// header") + r = run("info", "my_node", cwd=src) + assert "helper.hpp" in r.stdout + + +### srpkg list tests +def test_list_finds_packages(repo, src): + run("create", "node_a", cwd=src) + run("create", "node_b", cwd=src) + r = run("list", cwd=src) + assert r.returncode == 0 + assert "node_a" in r.stdout + assert "node_b" in r.stdout + +def test_list_finds_packages_other_dir(repo, src): + sub = src / "subsystem" + sub.mkdir() + run("create", "node_a", cwd=src) + run("create", "node_b", cwd=sub) + r = run("list", cwd=repo) + assert r.returncode == 0 + assert "node_a" in r.stdout + assert "node_b" in r.stdout + +def test_list_empty(repo, src): + r = run("list", cwd=src) + assert r.returncode == 0 + assert "No packages found" in r.stdout + +def test_list_ignores_dirs_without_marker(repo, src): + (src / "not_a_package").mkdir() + r = run("list", cwd=src) + assert "not_a_package" not in r.stdout \ No newline at end of file From c3bd77e8ef6ad9c40b21b1a427a834890e795914 Mon Sep 17 00:00:00 2001 From: Ryan Date: Sat, 21 Mar 2026 16:38:54 +1100 Subject: [PATCH 7/9] chore: added version notes to readme --- README.md | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 11c3dae..3be109d 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,13 @@ -# S130 Development tools V1.1.0 +# S130 Development tools V1.2.0 +## Version Nodes: v1.2.0 +- Cleaned up repo root checks from Henry's merge +- Removed repo root checks from `srlaunch`. +- Added `host/srutils.py` for common logic between `srpkg` and `srbuild` +- Added toml param file generation and autofilling, as well as autofilling a main file template in `srpkg` +- Added integration tests for `srpkg` and `srlaunch` + +## Introduction This repository contains high-level development tools for Sunswift embedded and DDS projects. It is separated into host/ and target/. host/ contains the dev tools we are using during development time on our host machines. target/ contains scripts that should be run on the target (NVIDIA Drive THOR computer). It is intended to be a submodule in the SR-Mjolnir repository. @@ -150,5 +158,4 @@ Then just `Ctrl-C` to shut down all nodes gracefully. It's that easy guys. ## Contributors Ryan Wong || z5417983 - Henry Jiang || z5416365 \ No newline at end of file From 369729cf5aa1ebd28ccaf3e3083c1cecf9cadbd8 Mon Sep 17 00:00:00 2001 From: Ryan Date: Sat, 21 Mar 2026 16:59:34 +1100 Subject: [PATCH 8/9] bugfix: fixed toml filling function bug and added it to tests --- host/srpkg | 4 +++- host/tests/test_srpkg_integration.py | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/host/srpkg b/host/srpkg index c49d5a4..a6b0fe6 100755 --- a/host/srpkg +++ b/host/srpkg @@ -153,7 +153,7 @@ def fill_param(paths: PkgPaths) -> None: # example_bool = true """ - with (paths.abs_pkg_path / FILES["param"]).open("w") as file: + with (paths.abs_pkg_path / FILES["param"].format(pkg_name=paths.pkg_name)).open("w") as file: file.write(text) def fill_main(paths: PkgPaths) -> None: @@ -166,6 +166,8 @@ int main(int argc, char* argv[]) {{ }} """ + with (paths.abs_pkg_path / FILES["main"]).open("w") as file: + file.write(text) def pkg_exist_elsewhere(paths: PkgPaths) -> tuple[bool, Optional[Path]]: """Recursively checks if valid package with the same name exists in src diff --git a/host/tests/test_srpkg_integration.py b/host/tests/test_srpkg_integration.py index 53e7d8d..7cf1640 100644 --- a/host/tests/test_srpkg_integration.py +++ b/host/tests/test_srpkg_integration.py @@ -44,8 +44,10 @@ def test_create_basic(repo: Path, src: Path) -> None: pkg = src / "my_node" assert (pkg / ".srpkg").exists() assert (pkg / "src").is_dir() + assert (pkg / "src" / "main.cpp").exists() assert (pkg / "include").is_dir() assert (pkg / "param").is_dir() + assert (pkg / "param" / "my_node_param.toml").exists() assert (pkg / "CMakeLists.txt").exists() assert (pkg / "README.md").exists() From 22ac61afd677b59d333730efec4889204d7d3e0c Mon Sep 17 00:00:00 2001 From: Ryan Date: Tue, 24 Mar 2026 14:15:00 +1100 Subject: [PATCH 9/9] bugfix: fixed potential bug with jobs command in srbuild, changed validate_name to not accept empty strings, fixed typo in readme --- .gitignore | 2 +- README.md | 2 +- host/srbuild | 7 +++---- host/srpkg | 2 +- target/srlaunch | 1 - 5 files changed, 6 insertions(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index 1f3fd59..4498781 100644 --- a/.gitignore +++ b/.gitignore @@ -59,4 +59,4 @@ htmlcov/ # OS .DS_Store -Thumbs.db \ No newline at end of file +Thumbs.db diff --git a/README.md b/README.md index 3be109d..2cc319a 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # S130 Development tools V1.2.0 -## Version Nodes: v1.2.0 +## Version Notes: v1.2.0 - Cleaned up repo root checks from Henry's merge - Removed repo root checks from `srlaunch`. - Added `host/srutils.py` for common logic between `srpkg` and `srbuild` diff --git a/host/srbuild b/host/srbuild index 7edda08..40d48dc 100755 --- a/host/srbuild +++ b/host/srbuild @@ -16,7 +16,6 @@ ############################################################################### import argparse -import sys import subprocess import time import shutil @@ -105,14 +104,14 @@ def build(targets: Optional[list[str]], jobs: int) -> None: jobs (int): number of jobs to run in parallel """ print("============= Building Targets ===============") - jobs_str = f"-j{jobs}" + jobs_str = f"{jobs}" start_time = time.time() # Build targets try: if targets is None: print("Build: Building all targets") subprocess.run([ - "cmake", "--build", str(BUILD_DIR_PATH), "--", jobs_str + "cmake", "--build", str(BUILD_DIR_PATH), "-j", jobs_str ], check=True) else: print("Build: Building specified targets") @@ -120,7 +119,7 @@ def build(targets: Optional[list[str]], jobs: int) -> None: # If someone is using --target, build times probably tiny anyway for target in targets: subprocess.run([ - "cmake", "--build", str(BUILD_DIR_PATH), "--target", target, "--", jobs_str + "cmake", "--build", str(BUILD_DIR_PATH), "--target", target, "-j", jobs_str ], check=True) except subprocess.CalledProcessError: die(f"Build: Error building targets") diff --git a/host/srpkg b/host/srpkg index a6b0fe6..8868d4d 100755 --- a/host/srpkg +++ b/host/srpkg @@ -216,7 +216,7 @@ def safe_rmdir(paths: PkgPaths) -> None: def validate_name(name: str) -> PkgPaths: ### Validate node package name - pattern = r"^[a-z0-9_]*$" # technically not correct but... + pattern = r"^[a-z0-9_]+$" if not re.match(pattern, name): die("Invalid package name: must be in 'snake_case'") diff --git a/target/srlaunch b/target/srlaunch index 9e656a4..01a7f0d 100755 --- a/target/srlaunch +++ b/target/srlaunch @@ -20,7 +20,6 @@ # - ./srlaunch target node1 node2 ############################################################################### -import json import subprocess import sys import signal