diff --git a/README.md b/README.md index 11c3dae..a1a3072 100644 --- a/README.md +++ b/README.md @@ -5,8 +5,8 @@ This repository contains high-level development tools for Sunswift embedded and It includes: -- `srpkg`: DDS package creation and management tool -- `srbuild`: Build tool for compiling and deploying DDS packages +- `srpkg`: DDS package creation and management tool +- `srbuild`: Build tool for compiling and deploying DDS packages - `srlaunch`: Tool for starting nodes - `srdds`: (WORK IN PROGRESS) Tool for managing active nodes @@ -149,6 +149,4 @@ Then just `Ctrl-C` to shut down all nodes gracefully. It's that easy guys. - `srdds` is currently work in progress ## Contributors -Ryan Wong || z5417983 - -Henry Jiang || z5416365 \ No newline at end of file +Ryan Wong || z5417983 \ No newline at end of file diff --git a/host/srbuild b/host/srbuild index ed7163d..78e6c26 100755 --- a/host/srbuild +++ b/host/srbuild @@ -16,7 +16,6 @@ ############################################################################### import argparse -import os import sys import subprocess import time @@ -24,12 +23,15 @@ import shutil from typing import Optional from pathlib import Path -CWD = Path.cwd().resolve() -REPO_ROOT = Path() -BUILD_DIR_PATH = Path() -CMAKELISTS_PATH = Path() -INSTALL_PREFIX = Path() -MARKER_FILE = ".sunswift-evsn" +CWD = Path.cwd() +# 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") + +BUILD_DIR_PATH = REPO_ROOT/"build" +CMAKELISTS_PATH = REPO_ROOT/"CMakeLists.txt" +INSTALL_PREFIX = REPO_ROOT/"deploy" # ================================================================================================= # HELPERS @@ -39,46 +41,6 @@ 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(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 safe_rmdir(path: Path) -> bool: """Absolutely every error check again just to confirm before deletion. @@ -246,10 +208,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") @@ -258,13 +216,7 @@ def main(): command_target.add_argument("targets", nargs="+", help="One or more node target names") args = parser.parse_args() - ### 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) - validate_repo_root(REPO_ROOT) - BUILD_DIR_PATH = REPO_ROOT / "build" - CMAKELISTS_PATH = REPO_ROOT / "CMakeLists.txt" - INSTALL_PREFIX = REPO_ROOT / "deploy" + ### SANITY CHECK - immediately fail if not used within SRP8-130 High level embedded repository try: CWD.relative_to(REPO_ROOT) except ValueError: 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 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