diff --git a/.gitignore b/.gitignore index 40bb4e3..835cd1f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .vscode/ -__pycache__/ \ No newline at end of file +__pycache__/ +*.egg-info/ \ No newline at end of file diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..570ca9b --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,10 @@ +repos: + - repo: local + hooks: + - id: clang_restage + name: Restage formatted files + entry: clang_restage + language: system + pass_filenames: false + always_run: true + stages: [pre-commit] \ No newline at end of file diff --git a/aliases.txt b/aliases.txt new file mode 100644 index 0000000..860c590 --- /dev/null +++ b/aliases.txt @@ -0,0 +1,8 @@ +build="docker compose run --rm ner-gcc-arm make" +cerbcon="sudo modprobe vhci_hcd && sudo usbip attach -r 192.168.100.12 -b 1-1.3" +disconnect="sudo usbip detach -p 0" +shepcon="sudo modprobe vhci_hcd && sudo usbip attach -r 192.168.100.12 -b 1-1.4" +peyton="echo \" Shut the fuck up Peyton, fucking hell\" " +flash="bash -c \"probe-rs download --verify --chip STM32F405RGTx ./build/*.elf && probe-rs reset --chip STM32F405RGTx\" " +reset-target="bash -c \"probe-rs reset --chip STM32F405RGTx\" " +debug="bash -c 'probe-rs gdb --gdb-connection-string 0.0.0.0:1337 --chip STM32F405RGTx & sleep 1; docker compose run --rm -P ner-gcc-arm "arm-none-eabi-gdb" /home/app$(ls ./build/*.elf | cut -c2-) -ex \"target remote host.docker.internal:1337\" && killall probe-rs && probe-rs reset --chip STM32F405RGTx' " diff --git a/clang_restage.py b/clang_restage.py new file mode 100644 index 0000000..5bac1cc --- /dev/null +++ b/clang_restage.py @@ -0,0 +1,50 @@ +import subprocess +import sys +import os + +def get_staged_files(): + # Run the git command to get staged files + result = subprocess.run(['git', 'diff', '--cached', '--name-only'], stdout=subprocess.PIPE, text=True) + + # Get the list of staged files from stdout + staged_files = result.stdout.splitlines() + + # Filter files based on their extensions + filtered_files = [f for f in staged_files if f.endswith(('.c', '.cpp', '.h'))] + + return filtered_files + +def restage_files(files): + if files: + subprocess.run(['git', 'add'] + files) + else: + print("No files to restage.") + +def main(): + + current_directory = os.getcwd() + + # Get a list of staged files + staged_files = get_staged_files() + + # Run clang-format on staged files + + if "bedded" in current_directory: + result = subprocess.run(['clang-format', '--style=file:clang-format', '-i'] + staged_files, + capture_output=True, text=True) + else: + + clang_format_path = os.path.join(current_directory, 'Drivers', 'Embedded-Base', 'clang-format') + result = subprocess.run(['clang-format', f'--style=file:{clang_format_path}', '-i'] + staged_files, + capture_output=True, text=True) + + if result.returncode == 0: + print("clang-format passed. Restaging files.") + restage_files(staged_files) + else: + print(current_directory) + print(f"clang-format failed. Please fix the issues and commit again.(Most likely, just try commiting again) {clang_format_path}") + sys.exit(1) + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/launchpad.py b/launchpad.py new file mode 100644 index 0000000..b517706 --- /dev/null +++ b/launchpad.py @@ -0,0 +1,115 @@ +import subprocess +import sys +import os +import platform +import shutil + +def install_platformio(venv_path): + """Install PlatformIO package and set up aliases.""" + try: + # Install the platformio package + subprocess.check_call([sys.executable, '-m', 'pip', 'install', 'platformio']) + + # Define aliases + aliases = [ + 'lpbuild="platformio run"', + 'lpflash="platformio run --target upload"', + 'lpinit="platformio project init"', + + ] + + os_type = platform.system() + if os_type == 'Windows': + activate_path = os.path.join(venv_path, 'Scripts', 'activate') # Bash script for Git Bash on Windows + else: + activate_path = os.path.join(venv_path, 'bin', 'activate') # bash script for Unix-like system + + + with open(activate_path, 'a') as activate_file: + activate_file.write('\n# Aliases\n') + for alias in aliases: + alias_name, alias_command = alias.strip().split('=', 1) + alias_command = alias_command.strip('"') + activate_file.write(f'alias {alias_name}="{alias_command}"\n') + + print("Aliases added to the activation script successfully.") + + except subprocess.CalledProcessError as e: + print(f"Failed to install PlatformIO: {e}", file=sys.stderr) + sys.exit(1) + +def uninstall_platformio(venv_path): + """Uninstall PlatformIO package and remove aliases.""" + try: + # Uninstall the platformio package + subprocess.check_call([sys.executable, '-m', 'pip', 'uninstall', '-y', 'platformio']) + + # Remove PlatformIO directory + remove_platformio_directory() + + # Determine OS and adjust alias handling + os_type = platform.system() + if os_type == 'Windows': + activate_path = os.path.join(venv_path, 'Scripts', 'activate') # Bash script for Git Bash on Windows + else: + activate_path = os.path.join(venv_path, 'bin', 'activate') # bash script for Unix-like system + + remove_aliases(activate_path) + + print("PlatformIO uninstalled and aliases removed. Please restart your terminal or source your profile script.") + + except subprocess.CalledProcessError as e: + print(f"Failed to uninstall PlatformIO: {e}", file=sys.stderr) + sys.exit(1) + +def remove_aliases(activate_path): + """Remove aliases from the virtual environment's activation script.""" + try: + if os.path.exists(activate_path): + with open(activate_path, 'r') as f: + lines = f.readlines() + with open(activate_path, 'w') as f: + for line in lines: + if not line.startswith('alias '): + f.write(line) + print("Aliases removed from the activation script successfully.") + else: + print("Activation script not found.", file=sys.stderr) + sys.exit(1) + except Exception as e: + print(f"Failed to remove aliases: {e}", file=sys.stderr) + sys.exit(1) + +def remove_platformio_directory(): + """Remove the PlatformIO directory.""" + platformio_dir = os.path.expanduser('~/.platformio') + if os.path.isdir(platformio_dir): + try: + shutil.rmtree(platformio_dir) + print("PlatformIO directory removed.") + except OSError as e: + print(f"Failed to remove PlatformIO directory: {e}", file=sys.stderr) + sys.exit(1) + else: + print("PlatformIO directory does not exist.") +def main(): + if len(sys.argv) != 2: + print("Usage: launchpad.py [install|uninstall]", file=sys.stderr) + sys.exit(1) + + os_type = platform.system() + current_directory = os.path.dirname(os.path.abspath(__file__)) + parent_directory = os.path.dirname(current_directory) + venv_path = os.path.join(parent_directory, 'ner-venv') + + action = sys.argv[1].lower() + if action == 'install': + install_platformio(venv_path) + elif action == 'uninstall': + uninstall_platformio(venv_path) + else: + print("Invalid action. Use 'install' or 'uninstall'.", file=sys.stderr) + sys.exit(1) + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/load_alias.py b/load_alias.py new file mode 100644 index 0000000..c98cae2 --- /dev/null +++ b/load_alias.py @@ -0,0 +1,65 @@ +import os +import platform +import sys + +def load_aliases(venv_path, aliases_file): + os_type = platform.system() + if os_type == 'Windows': + activate_path = os.path.join(venv_path, 'Scripts', 'activate') # Bash script for Git Bash on Windows + else: + activate_path = os.path.join(venv_path, 'bin', 'activate') # bash script for Unix-like systems + + try: + # Read existing aliases from the activation script + if os.path.exists(activate_path): + with open(activate_path, 'r') as activate_file: + existing_aliases = activate_file.readlines() + else: + existing_aliases = [] + + # Convert the existing aliases to a set for easy comparison + existing_aliases_set = set() + for line in existing_aliases: + if line.startswith('alias '): + alias_definition = line.strip() + alias_name = alias_definition.split('=')[0].replace('alias ', '') + existing_aliases_set.add(alias_name) + + # Read aliases from the provided aliases file + with open(aliases_file, 'r') as f: + aliases = f.readlines() + + # Prepare to write new aliases that aren't already in the activate script + new_aliases = [] + for alias in aliases: + alias_name, alias_command = alias.strip().split('=', 1) + alias_name = alias_name.strip() + alias_command = alias_command.strip('"') + + if alias_name not in existing_aliases_set: + new_aliases.append(f'alias {alias_name}="{alias_command}"\n') + + # Append new aliases to the activation script if there are any + if new_aliases: + with open(activate_path, 'a') as activate_file: + activate_file.write('\n# Aliases\n') + activate_file.writelines(new_aliases) + print(f"Added {len(new_aliases)} new alias(es) to the activation script.") + else: + print("No new aliases to add; all are already present.") + + except Exception as e: + print(f"Failed to update aliases: {e}", file=sys.stderr) + sys.exit(1) + +def main(): + current_directory = os.path.dirname(os.path.abspath(__file__)) + parent_directory = os.path.dirname(current_directory) + venv_path = os.path.join(parent_directory, 'ner-venv') + aliases_file = os.path.join(current_directory, 'aliases.txt') + + load_aliases(venv_path, aliases_file) + print("Close and reopen your terminal or the venv for changes to take effect.") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/miniterm.py b/miniterm.py new file mode 100644 index 0000000..5e8e000 --- /dev/null +++ b/miniterm.py @@ -0,0 +1,62 @@ +import os +import subprocess +import platform +import sys + +def list_usb_devices(): + """List available USB serial devices based on the operating system.""" + os_name = platform.system() + + if os_name == 'Windows': + # List COM ports on Windows + try: + result = subprocess.run(['wmic', 'path', 'Win32_SerialPort'], capture_output=True, text=True) + devices = [] + for line in result.stdout.splitlines(): + if 'DeviceID' in line: + devices.append(line.split()[-1]) + return devices + except Exception as e: + print(f"Failed to list USB devices on Windows: {e}", file=sys.stderr) + sys.exit(1) + + elif os_name == 'Linux' or os_name == 'Darwin': # Darwin is macOS + # List USB devices on Unix-like systems + try: + result = subprocess.run(['ls /dev/tty*'], shell=True, capture_output=True, text=True) + + devices = [device for device in result.stdout.splitlines() if 'ttyUSB' in device or 'ttyACM' in device] + return devices + except Exception as e: + print(f"Failed to list USB devices on {os_name}: {e}", file=sys.stderr) + sys.exit(1) + + else: + print(f"Unsupported operating system: {os_name}", file=sys.stderr) + sys.exit(1) + +def run_miniterm(device, baudrate=115200): + """Run pyserial-miniterm with the specified device and baudrate.""" + try: + subprocess.run(['pyserial-miniterm', device, str(baudrate)], check=True) + except subprocess.CalledProcessError as e: + print(f"Failed to run pyserial-miniterm: {e}", file=sys.stderr) + sys.exit(1) + +def main(): + # Detect the operating system and find USB devices + devices = list_usb_devices() + + if not devices: + print("No USB devices found.", file=sys.stderr) + sys.exit(1) + + # Default to the first device if available + selected_device = devices[0] + print(f"Selected USB device: {selected_device}") + + # Run pyserial-miniterm with the selected device + run_miniterm(selected_device) + +if __name__ == '__main__': + main() diff --git a/ner_setup.py b/ner_setup.py new file mode 100644 index 0000000..e9c5e9b --- /dev/null +++ b/ner_setup.py @@ -0,0 +1,183 @@ +import os +import platform +import subprocess +import sys +import venv + +try: + import distro # for Linux distribution detection +except ImportError: + distro = None + +def run_command(command, check=True, shell=False): + try: + result = subprocess.run(command, check=check, shell=shell, text=True, capture_output=True) + print(result.stdout) + if result.stderr: + print(result.stderr, file=sys.stderr) + except subprocess.CalledProcessError as e: + print(f"Command failed: {e.cmd}\nReturn code: {e.returncode}\nOutput: {e.output}\nError: {e.stderr}", file=sys.stderr) + sys.exit(1) + +def check_docker_and_rust(): + print("This script requires Docker and Rust to be installed.") + answer = input("Do you have Docker and Rust installed? (yes/no): ").strip().lower() + if 'y' not in answer: + sys.exit(1) + + os_type = platform.system() + if os_type == 'Windows': + print("Windows OS detected. If on windows, you must be using bash (included with git), instead of cmd or powershell") + answer = input("Are you using bash? (yes/no): ").strip().lower() + if 'y' not in answer: + sys.exit(1) + +def docker_pull(image_url): + os_type = platform.system() + if os_type=='Windows' or os_type == 'Darwin': + print("Please open the Docker Desktop application before proceeding") + answer =input("Is the Docker Desktop app running? (yes/no)") + print("Pulling Docker image...") + run_command(["docker", "pull", image_url]) + print("Docker image pulled successfully.") + +def load_aliases(venv_path, aliases_file): + os_type = platform.system() + if os_type == 'Windows': + activate_path = os.path.join(venv_path, 'Scripts', 'activate') # Bash script for Git Bash on Windows + else: + activate_path = os.path.join(venv_path, 'bin', 'activate') # bash script for Unix-like systems + + try: + with open(aliases_file, 'r') as f: + aliases = f.readlines() + + with open(activate_path, 'a') as activate_file: + activate_file.write('\n# Aliases\n') + for alias in aliases: + alias_name, alias_command = alias.strip().split('=', 1) + alias_command = alias_command.strip('"') + activate_file.write(f'alias {alias_name}="{alias_command}"\n') + + print("Aliases added to the activation script successfully.") + except Exception as e: + print(f"Failed to add aliases: {e}", file=sys.stderr) + sys.exit(1) + +def create_venv(venv_path): + try: + venv.EnvBuilder(with_pip=True).create(venv_path) + print(f"Virtual environment created at {venv_path}") + except Exception as e: + print(f"ERROR: Failed to create virtual environment: {e}", file=sys.stderr) + sys.exit(1) + +def run_setup(venv_python): + print("Running setup.py...") + try: + # Run the pip install -e . command + run_command([venv_python, '-m', 'pip', 'install', '-e', '.']) + except subprocess.CalledProcessError as e: + print(f"Failed to run pip install -e .: {e}", file=sys.stderr) + sys.exit(1) + + +def install_precommit(venv_python): + print("Installing pre-commit hook...") + try: + run_command([venv_python, '-m', 'pre_commit', 'install']) + except Exception as e: + print(f"Failed to install pre-commit: {e}", file=sys.stderr) + sys.exit(1) + +def install_probe_rs(): + os_type = platform.system() + print("Installing probe-rs...") + try: + if os_type == "Windows": + # Using bash to run PowerShell for downloading and executing the script + command = [ + "powershell", "-Command", "Invoke-RestMethod https://github.com/probe-rs/probe-rs/releases/latest/download/probe-rs-tools-installer.ps1 | Invoke-Expression"] + run_command(command, shell=False) + else: + # For Unix-like systems (Linux, macOS) + command = ["bash", "-c", "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/probe-rs/probe-rs/releases/latest/download/probe-rs-tools-installer.sh | sh"] + run_command(command, shell=False) + except Exception as e: + print(f"Failed to install probe-rs: {e}", file=sys.stderr) + sys.exit(1) + +def install_usbip(): + if distro: + distro_name = distro.id() + if distro_name in ["ubuntu", "debian"]: + run_command(["sudo", "apt-get", "install", "-y", "linux-tools-generic"]) + elif distro_name == "fedora": + run_command(["sudo", "dnf", "install", "-y", "linux-tools-generic"]) + elif distro_name == "arch": + run_command(["sudo", "pacman", "-S", "--noconfirm", "linux-tools-generic"]) + else: + print("We haven't added USBIP install support for your distro, but if you're actually on linux, you can install it manually!") + else: + print("You should only see this if im stupid! Let someone know if you see this message -> ID=STUPID1") + +def main(): + print("Welcome to NER Embedded Software! If this script shits the bed, let your system head or lead know!") + print("-----------------------------------------------------------------------------------------------") + print("Every step below will have an option to skip. While most users will *not* want to skip anything") + print("Everyone's system is different, and if you already have something installed, or know exactly") + print("what you are doing, feel free to manually go about this as needed.") + + # Step 0: Check for Docker and Rust + check_docker_and_rust() + + # Step 1: pull image + answer = input("Would you like to pull the docker image? (yes/no)") + if 'y' in answer: + image_url = "ghcr.io/northeastern-electric-racing/embedded-base:main" + docker_pull(image_url) + + os_type = platform.system() + current_directory = os.path.dirname(os.path.abspath(__file__)) + parent_directory = os.path.dirname(current_directory) + venv_path = os.path.join(parent_directory, 'ner-venv') + + + # Step 2: Create the Python virtual environment + answer = input("Would you like to setup the virtual environment (yes/no)") + if 'y' in answer: + create_venv(venv_path) + + answer = input("Would you like to add necesarry alias commands? (yes/no)") + if 'y' in answer: + + # Step 3: Modify activation and deactivation scripts + aliases_file = os.path.join(current_directory, 'aliases.txt') + load_aliases(venv_path, aliases_file) + + answer = input("Would you like to install all python packages in the venv? (yes/no)") + if 'y' in answer: + # Use the venv's Python + venv_python = os.path.join(venv_path, 'Scripts', 'python') if os_type == "Windows" else os.path.join(venv_path, 'bin', 'python') + + # Step 4: run setup.py (installs requirements and entry points) + run_setup(venv_python) + + # Step 5: Run pre-commit install + install_precommit(venv_python) + + answer = input("Would you like to install probe-rs? (do this manually if on a weird linux distro!) (yes/no)") + if 'y' in answer: + # Step 5: Install probe-rs + install_probe_rs() + + # Step 6: Install usbip if on Linux + if os_type == "Linux": + answer = input("Would you like to install usbip? (yes/no)") + if 'y' in answer: + install_usbip() + + print("Setup completed successfully!") + +if __name__ == "__main__": + main() diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..51af454 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +distro +pre-commit +clang-format diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..58aa45f --- /dev/null +++ b/setup.py @@ -0,0 +1,25 @@ +from setuptools import setup + +# Read the contents of requirements.txt +def parse_requirements(filename): + with open(filename, 'r') as file: + return [line.strip() for line in file if line.strip() and not line.startswith('#')] + +setup( + name='ner-setup', + version='0.1', + py_modules=['ner_setup', 'launchpad', 'clang_restage', 'miniterm' , 'load_alias'], + install_requires=parse_requirements('requirements.txt'), + entry_points={ + 'console_scripts': [ + 'ner_setup=ner_setup:main', # Command 'ner_setup' runs 'main' function in ner_setup.py + 'clang_restage=clang_restage:main', + 'serial=miniterm:main', + 'launchpad=launchpad:main', + 'load-alias=load_alias:main', + + ], + }, + + +) \ No newline at end of file