|
| 1 | +--- |
| 2 | +layout: post |
| 3 | +title: Dynamic Dependency Management for Modular Architecture |
| 4 | +date: 2024-10-26 20:44 |
| 5 | +category: Guides |
| 6 | +tags: [guide, dependencies, modular, architecture, automation, python, unix] |
| 7 | +--- |
| 8 | + |
| 9 | +In modern software development, managing dependencies and configuring environments can often be a tedious and error-prone task. To simplify this process, we've revamped our install.sh script, which automates the installation of Python and Unix dependencies for our modular architecture. This enhanced script not only sets up a Python virtual environment but also intelligently reads configuration files to install dependencies only for active modules, streamlining the setup process. |
| 10 | + |
| 11 | +## Advantages of Dynamic Dependency Management |
| 12 | + |
| 13 | +1. **Modular Architecture**: Our system is designed to be modular, with each component encapsulated in a separate module. This approach allows for easier maintenance, testing, and scalability. |
| 14 | +2. **Dependency Isolation**: By specifying dependencies at the module level, we can ensure that each module has the necessary dependencies without affecting other modules. |
| 15 | +3. **Automated Installation**: The enhanced `install.sh` script automates the installation of dependencies based on the configuration files, reducing manual intervention and potential errors. Because only dependencies from active modules are installed, the setup process is optimized and efficient. |
| 16 | + |
| 17 | +## Background |
| 18 | + |
| 19 | +To handle both **Python** and **Unix (system)** dependencies, we’ll make a few updates: |
| 20 | + |
| 21 | +1. **Add a `dependencies` section** to each module's YAML configuration for listing Unix dependencies. |
| 22 | +2. Use **Python** (specifically the `yaml` library) in the `install.sh` script to read dependencies from each config file. |
| 23 | +3. Ensure **`apt-get install`** is used for Unix dependencies, and **`pip install`** for Python dependencies. |
| 24 | + |
| 25 | +Here's what this would look like: |
| 26 | + |
| 27 | +### Updated YAML Config Files |
| 28 | + |
| 29 | +Each module config can now specify both Python and Unix dependencies. |
| 30 | + |
| 31 | +**Example `servos.yml`:** |
| 32 | + |
| 33 | +```yaml |
| 34 | +servos: |
| 35 | + port: /dev/ttyAMA0 |
| 36 | + conf: |
| 37 | + leg_l_hip: { id: 0, pin: 9, range: [0, 180], start: 40 } |
| 38 | + # other servo configurations... |
| 39 | + dependencies: |
| 40 | + python: |
| 41 | + - pyserial |
| 42 | + - pigpio |
| 43 | + unix: |
| 44 | + - libpigpio-dev |
| 45 | +``` |
| 46 | +
|
| 47 | +**Example `motion.yml`:** |
| 48 | + |
| 49 | +```yaml |
| 50 | +motion: |
| 51 | + pin: 26 |
| 52 | + dependencies: |
| 53 | + python: |
| 54 | + - RPi.GPIO |
| 55 | + unix: |
| 56 | + - wiringpi |
| 57 | +``` |
| 58 | + |
| 59 | +### Modified `install.sh` Script |
| 60 | + |
| 61 | +The install script below: |
| 62 | +1. Parses `python` and `unix` dependencies from each module's YAML file. |
| 63 | +2. Installs Unix dependencies with `apt-get install` and Python dependencies with `pip install`. |
| 64 | +3. Uses a **Python helper** embedded within the script to read YAML files (using `pyyaml`). |
| 65 | + |
| 66 | +Here’s the modified `install.sh` script: |
| 67 | + |
| 68 | +```bash |
| 69 | +#!/bin/bash |
| 70 | +
|
| 71 | +# Set up Python virtual environment |
| 72 | +python3 -m venv --system-site-packages myenv |
| 73 | +source myenv/bin/activate |
| 74 | +
|
| 75 | +# Initialize arrays for dependencies and active module names |
| 76 | +PYTHON_DEPENDENCIES=() |
| 77 | +UNIX_DEPENDENCIES=() |
| 78 | +ACTIVE_MODULES=() |
| 79 | +
|
| 80 | +# Helper function to parse dependencies from YAML files using Python |
| 81 | +parse_dependencies() { |
| 82 | + myenv/bin/python3 - <<EOF |
| 83 | +import yaml, sys, os |
| 84 | +
|
| 85 | +config_file = "$1" |
| 86 | +module_name = os.path.basename(config_file).replace('.yml', '') # Get the module name from the filename |
| 87 | +try: |
| 88 | + with open(config_file) as f: |
| 89 | + config = yaml.safe_load(f) |
| 90 | + # Check if the top level of the config is a dictionary to avoid AttributeError |
| 91 | + if isinstance(config, dict): |
| 92 | + for section in config.values(): |
| 93 | + # Check if module is active before parsing dependencies |
| 94 | + if isinstance(section, dict) and section.get('enabled') is True: |
| 95 | + print(f"MODULE:{module_name}") |
| 96 | + if 'dependencies' in section: |
| 97 | + for dep_type, deps in section['dependencies'].items(): |
| 98 | + if dep_type == 'python': |
| 99 | + for dep in deps: |
| 100 | + print(f"PYTHON:{dep}") |
| 101 | + elif dep_type == 'unix': |
| 102 | + for dep in deps: |
| 103 | + print(f"UNIX:{dep}") |
| 104 | +except yaml.YAMLError as e: |
| 105 | + print(f"Error reading {config_file}: {e}", file=sys.stderr) |
| 106 | +EOF |
| 107 | +} |
| 108 | +
|
| 109 | +# Iterate over each YAML config file in the config directory |
| 110 | +for config_file in config/*.yml; do |
| 111 | + while IFS= read -r dependency; do |
| 112 | + # Separate Python and Unix dependencies and capture active module names |
| 113 | + if [[ $dependency == MODULE:* ]]; then |
| 114 | + ACTIVE_MODULES+=("${dependency#MODULE:}") |
| 115 | + elif [[ $dependency == PYTHON:* ]]; then |
| 116 | + PYTHON_DEPENDENCIES+=("${dependency#PYTHON:}") |
| 117 | + elif [[ $dependency == UNIX:* ]]; then |
| 118 | + UNIX_DEPENDENCIES+=("${dependency#UNIX:}") |
| 119 | + fi |
| 120 | + done < <(parse_dependencies "$config_file") |
| 121 | +done |
| 122 | +
|
| 123 | +# Remove duplicate dependencies |
| 124 | +UNIQUE_PYTHON_DEPENDENCIES=($(echo "${PYTHON_DEPENDENCIES[@]}" | tr ' ' '\n' | sort -u | tr '\n' ' ')) |
| 125 | +UNIQUE_UNIX_DEPENDENCIES=($(echo "${UNIX_DEPENDENCIES[@]}" | tr ' ' '\n' | sort -u | tr '\n' ' ')) |
| 126 | +UNIQUE_ACTIVE_MODULES=($(echo "${ACTIVE_MODULES[@]}" | tr ' ' '\n' | sort -u | tr '\n' ' ')) |
| 127 | +
|
| 128 | +# Update apt-get and install Unix dependencies |
| 129 | +if [ ${#UNIQUE_UNIX_DEPENDENCIES[@]} -ne 0 ]; then |
| 130 | + sudo apt-get update |
| 131 | + for dep in "${UNIQUE_UNIX_DEPENDENCIES[@]}"; do |
| 132 | + sudo apt-get install -y "$dep" |
| 133 | + done |
| 134 | +fi |
| 135 | +
|
| 136 | +# Install Python dependencies explicitly using the virtual environment's pip |
| 137 | +for dep in "${UNIQUE_PYTHON_DEPENDENCIES[@]}"; do |
| 138 | + myenv/bin/python3 -m pip install "$dep" |
| 139 | +done |
| 140 | +
|
| 141 | +# Set execute permissions for additional scripts |
| 142 | +chmod 777 startup.sh stop.sh |
| 143 | +
|
| 144 | +# Summary of modules and dependencies installed |
| 145 | +echo -e "\n==== Installation Summary ====" |
| 146 | +echo "Active modules installed: ${#UNIQUE_ACTIVE_MODULES[@]}" |
| 147 | +for module in "${UNIQUE_ACTIVE_MODULES[@]}"; do |
| 148 | + echo " - $module" |
| 149 | +done |
| 150 | +
|
| 151 | +echo -e "\nPython dependencies installed:" |
| 152 | +for dep in "${UNIQUE_PYTHON_DEPENDENCIES[@]}"; do |
| 153 | + echo " - $dep" |
| 154 | +done |
| 155 | +
|
| 156 | +echo -e "\nUnix dependencies installed:" |
| 157 | +for dep in "${UNIQUE_UNIX_DEPENDENCIES[@]}"; do |
| 158 | + echo " - $dep" |
| 159 | +done |
| 160 | +echo "=============================" |
| 161 | +
|
| 162 | +``` |
| 163 | + |
| 164 | +### Explanation of Changes |
| 165 | + |
| 166 | +1. **`parse_dependencies` Function**: A Python function is embedded in the `install.sh` script to parse YAML files and print dependencies in a formatted way for easy processing. |
| 167 | +2. **Dependency Arrays**: |
| 168 | + - `PYTHON_DEPENDENCIES` and `UNIX_DEPENDENCIES` store dependencies, allowing us to separate out `apt-get` and `pip` installations. |
| 169 | +3. **Removing Duplicates**: Using `sort -u` to ensure dependencies aren’t installed more than once. |
| 170 | +4. **Installing Dependencies**: |
| 171 | + - **Unix** dependencies are updated and installed using `apt-get`. |
| 172 | + - **Python** dependencies are installed with `pip`. |
| 173 | + |
| 174 | +### Folder Structure Example |
| 175 | + |
| 176 | +Here’s the directory structure for this configuration: |
| 177 | + |
| 178 | +```plaintext |
| 179 | +project_root/ |
| 180 | +├── config/ |
| 181 | +│ ├── servos.yml |
| 182 | +│ ├── motion.yml |
| 183 | +├── install.sh |
| 184 | +└── main.py |
| 185 | +``` |
| 186 | + |
| 187 | +This approach ensures that dependencies are handled based on each module’s needs, making it easier to maintain and update dependencies dynamically. |
| 188 | + |
| 189 | +### Example Output |
| 190 | + |
| 191 | +After running the `install.sh` script, you should see a summary of the installed modules and dependencies. |
| 192 | + |
| 193 | +Remember, only **active modules** will have their dependencies installed. |
| 194 | + |
| 195 | +```plaintext |
| 196 | +==== Installation Summary ==== |
| 197 | +Active modules installed: 12 |
| 198 | + - animate |
| 199 | + - braillespeak |
| 200 | + - buzzer |
| 201 | + - motion |
| 202 | + - neopixel |
| 203 | + - piservo |
| 204 | + - pitemperature |
| 205 | + - serial |
| 206 | + - servos |
| 207 | + - tracking |
| 208 | + - translator |
| 209 | + - vision |
| 210 | +
|
| 211 | +Python dependencies installed: |
| 212 | + - adafruit-circuitpython-seesaw |
| 213 | + - googletrans==3.1.0a0 |
| 214 | + - gpiozero |
| 215 | + - pigpio |
| 216 | + - pypubsub |
| 217 | + - python3-munkres |
| 218 | + - python3-opencv |
| 219 | +
|
| 220 | +Unix dependencies installed: |
| 221 | + - imx500-all |
| 222 | +============================= |
| 223 | +``` |
0 commit comments