build(dev): enhance local development environment and setup workflow (#358)
Improve the developer experience by providing structured setup scripts, VS Code workspace configurations, and detailed documentation. - Add VS Code workspace configuration and recommended settings - Update README.md with system requirements and VS Code setup instructions - Refactor `script/setup` to include Python 3.11+ validation and system dependency checks - Add option to skip PulseAudio wait in `docker-entrypoint.sh` for local development - Update `.gitignore` to ensure consistent version file handling
This commit is contained in:
1 parent
a66c0e4f28
commit
486ff4bb53
9 files changed
+199
-26
No files matched your search
+1
-1
@@ -23,4 +23,4 @@ venv/
|
|||||||
.env
|
.env
|
||||||
dev.env
|
dev.env
|
||||||
version.txt
|
version.txt
|
||||||
version_githash.txt
|
version_githash.txt
|
||||||
Vendored
+7
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"recommendations": [
|
||||||
|
"ms-python.python",
|
||||||
|
"ms-python.vscode-pylance",
|
||||||
|
"kilo.kilocode"
|
||||||
|
]
|
||||||
|
}
|
||||||
Vendored
+14
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "Run Linux Voice Assistant",
|
||||||
|
"type": "python",
|
||||||
|
"request": "launch",
|
||||||
|
"program": "${workspaceFolder}/linux_voice_assistant/__main__.py",
|
||||||
|
"console": "integratedTerminal",
|
||||||
|
"justMyCode": true,
|
||||||
|
"envFile": "${workspaceFolder}/.env"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Vendored
+13
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"python.defaultInterpreterPath": "./.venv/bin/python",
|
||||||
|
"python.testing.pytestEnabled": true,
|
||||||
|
"python.testing.pytestArgs": ["tests"],
|
||||||
|
"python.testing.pytestAutoInclude": false,
|
||||||
|
"python.linting.enabled": true,
|
||||||
|
"python.linting.flake8Enabled": true,
|
||||||
|
"python.linting.mypyEnabled": true,
|
||||||
|
"python.formatting.provider": "none",
|
||||||
|
"python.terminal.activateEnvironment": true,
|
||||||
|
"python.terminal.executeInShell": true,
|
||||||
|
"python.pythonPath": "./.venv/bin/python"
|
||||||
|
}
|
||||||
Vendored
+62
@@ -0,0 +1,62 @@
|
|||||||
|
{
|
||||||
|
"version": "2.0.0",
|
||||||
|
"tasks": [
|
||||||
|
{
|
||||||
|
"label": "Setup (incl. dev dependencies)",
|
||||||
|
"type": "shell",
|
||||||
|
"command": "./script/setup --dev",
|
||||||
|
"group": "build",
|
||||||
|
"presentation": {
|
||||||
|
"echo": true,
|
||||||
|
"reveal": "always",
|
||||||
|
"focus": false,
|
||||||
|
"panel": "new",
|
||||||
|
"clear": true
|
||||||
|
},
|
||||||
|
"problemMatcher": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Run Linter",
|
||||||
|
"type": "shell",
|
||||||
|
"command": "./script/lint",
|
||||||
|
"group": "build",
|
||||||
|
"presentation": {
|
||||||
|
"echo": true,
|
||||||
|
"reveal": "always",
|
||||||
|
"focus": false,
|
||||||
|
"panel": "new"
|
||||||
|
},
|
||||||
|
"problemMatcher": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Run Tests",
|
||||||
|
"type": "shell",
|
||||||
|
"command": "./script/tests",
|
||||||
|
"group": "test",
|
||||||
|
"presentation": {
|
||||||
|
"echo": true,
|
||||||
|
"reveal": "always",
|
||||||
|
"focus": false,
|
||||||
|
"panel": "new"
|
||||||
|
},
|
||||||
|
"problemMatcher": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Run Application (Docker)",
|
||||||
|
"type": "shell",
|
||||||
|
"command": "docker-compose",
|
||||||
|
"args": ["-f", "docker-compose.dev.yml", "up"],
|
||||||
|
"group": "none",
|
||||||
|
"presentation": {
|
||||||
|
"echo": true,
|
||||||
|
"reveal": "always",
|
||||||
|
"focus": false,
|
||||||
|
"panel": "new"
|
||||||
|
},
|
||||||
|
"problemMatcher": [],
|
||||||
|
"options": {
|
||||||
|
"envFile": "${workspaceFolder}/.env"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -131,6 +131,36 @@ The documentation for the build process can be found in the [GitHub Actions Work
|
|||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
|
### System Requirements
|
||||||
|
|
||||||
|
**System packages (Linux):**
|
||||||
|
- libmpv-dev (`sudo apt install libmpv-dev`)
|
||||||
|
- PulseAudio/PipeWire (`sudo apt install pulseaudio pipewire`)
|
||||||
|
- ALSA utils (`sudo apt install alsa-utils`)
|
||||||
|
|
||||||
|
**Python:**
|
||||||
|
- Python 3.11+ must be installed on your system (`python3 --version` should show 3.11+)
|
||||||
|
- On Ubuntu/Debian: `sudo apt install python3.11 python3.11-venv python3.11-dev`
|
||||||
|
|
||||||
|
### VS Code Setup
|
||||||
|
|
||||||
|
VS Code development uses a local Python virtual environment (`.venv/`):
|
||||||
|
|
||||||
|
1. Open workspace in VS Code
|
||||||
|
2. Accept Workspace Trust (bottom-left status bar)
|
||||||
|
3. Install recommended extensions when prompted:
|
||||||
|
- `ms-python.python` - Python language support
|
||||||
|
- `ms-python.vscode-pylance` - Python language server
|
||||||
|
- `kilo.kilocode` - Kilo AI assistant
|
||||||
|
4. **Terminal** → **New Terminal** (or `` Ctrl+` ``) - Opens integrated terminal (uses .venv when available)
|
||||||
|
5. Run `./script/setup --dev` to create `.venv` and install dev dependencies
|
||||||
|
6. VS Code automatically detects `.venv/bin/python` as the Python interpreter
|
||||||
|
|
||||||
|
**Available VS Code Tasks:**
|
||||||
|
- `Ctrl+Shift+B` - Run Setup or Linter
|
||||||
|
- `Ctrl+Shift+T` - Run Tests
|
||||||
|
- `Ctrl+Shift+P` → "Tasks: Run Task" - Show all tasks (Setup, Linter, Tests, Run App)
|
||||||
|
|
||||||
### Code Quality Checks
|
### Code Quality Checks
|
||||||
|
|
||||||
The project uses the following tools to ensure code quality:
|
The project uses the following tools to ensure code quality:
|
||||||
|
|||||||
+20
-18
@@ -145,26 +145,28 @@ fi
|
|||||||
|
|
||||||
|
|
||||||
### Wait for PulseAudio
|
### Wait for PulseAudio
|
||||||
# Wait for PulseAudio to be available before starting the application
|
# Skip wait if SKIP_PULSE_AUDIO_WAIT is set (for local development)
|
||||||
CP_MAX_RETRIES=30
|
if [ "$SKIP_PULSE_AUDIO_WAIT" != "1" ]; then
|
||||||
CP_RETRY_DELAY=1
|
CP_MAX_RETRIES=30
|
||||||
### while maybe besser?
|
CP_RETRY_DELAY=1
|
||||||
echo "Checking PulseAudio service status..."
|
### while maybe besser?
|
||||||
for i in $(seq 1 $CP_MAX_RETRIES); do
|
echo "Checking PulseAudio service status..."
|
||||||
# Check if PulseAudio is running
|
for i in $(seq 1 $CP_MAX_RETRIES); do
|
||||||
if pactl info >/dev/null 2>&1; then
|
# Check if PulseAudio is running
|
||||||
echo "✅ PulseAudio is running"
|
if pactl info >/dev/null 2>&1; then
|
||||||
break
|
echo "✅ PulseAudio is running"
|
||||||
fi
|
break
|
||||||
|
fi
|
||||||
|
|
||||||
if [ $i -eq $CP_MAX_RETRIES ]; then
|
if [ $i -eq $CP_MAX_RETRIES ]; then
|
||||||
echo "❌ PulseAudio did not start after $CP_MAX_RETRIES seconds"
|
echo "❌ PulseAudio did not start after $CP_MAX_RETRIES seconds"
|
||||||
exit 2
|
exit 2
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "⏳ PulseAudio not running yet, retrying in $CP_RETRY_DELAY s..."
|
echo "⏳ PulseAudio not running yet, retrying in $CP_RETRY_DELAY s..."
|
||||||
sleep $CP_RETRY_DELAY
|
sleep $CP_RETRY_DELAY
|
||||||
done
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
### Start application
|
### Start application
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"folders": [
|
||||||
|
{
|
||||||
|
"path": "."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
+45
-7
@@ -1,8 +1,10 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
import argparse
|
import argparse
|
||||||
import subprocess
|
import subprocess
|
||||||
import venv
|
|
||||||
import os
|
import os
|
||||||
|
import shutil
|
||||||
|
import sys
|
||||||
|
import ctypes.util
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
_DIR = Path(__file__).parent
|
_DIR = Path(__file__).parent
|
||||||
@@ -15,10 +17,47 @@ parser.add_argument("--cxxflags", type=str, help="CXXFLAGS for compilation (defa
|
|||||||
parser.add_argument("--makeflags", type=str, help="MAKEFLAGS for compilation (default: -j1)")
|
parser.add_argument("--makeflags", type=str, help="MAKEFLAGS for compilation (default: -j1)")
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
# Find Python 3.11+ interpreter
|
||||||
|
def find_python_311():
|
||||||
|
for candidate in ["python3.13", "python3.12", "python3.11"]:
|
||||||
|
path = shutil.which(candidate)
|
||||||
|
if path:
|
||||||
|
result = subprocess.run([path, "--version"], capture_output=True, text=True)
|
||||||
|
version_str = result.stdout.strip() or result.stderr.strip()
|
||||||
|
ver_parts = version_str.replace("Python ", "").split(".")
|
||||||
|
if len(ver_parts) >= 2 and int(ver_parts[0]) == 3 and int(ver_parts[1]) >= 11:
|
||||||
|
return path
|
||||||
|
return None
|
||||||
|
|
||||||
|
python_311_exe = find_python_311()
|
||||||
|
if not python_311_exe:
|
||||||
|
print("ERROR: Python 3.11+ is required but not found on this system.")
|
||||||
|
print(" Ubuntu/Debian: sudo apt install python3.11 python3.11-venv python3.11-dev")
|
||||||
|
print(" macOS: brew install python@3.11")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
print(f"Using Python: {python_311_exe}")
|
||||||
|
subprocess.run([python_311_exe, "--version"])
|
||||||
|
|
||||||
|
# Check system dependencies (warning only, CI installs them separately)
|
||||||
|
missing = []
|
||||||
|
try:
|
||||||
|
if not ctypes.util.find_library("mpv"):
|
||||||
|
missing.append("libmpv-dev")
|
||||||
|
if not ctypes.util.find_library("asound"):
|
||||||
|
missing.append("libasound2-dev")
|
||||||
|
except Exception:
|
||||||
|
pass # Ignore errors in environments without ctypes
|
||||||
|
|
||||||
|
if missing:
|
||||||
|
print(f"\nWARNING: Missing system libraries (required for audio): {', '.join(missing)}")
|
||||||
|
print("For Docker/local dev: sudo apt install libmpv-dev pulseaudio pipewire alsa-utils")
|
||||||
|
|
||||||
# Create virtual environment
|
# Create virtual environment
|
||||||
builder = venv.EnvBuilder(with_pip=True)
|
if _VENV_DIR.exists():
|
||||||
context = builder.ensure_directories(_VENV_DIR)
|
shutil.rmtree(_VENV_DIR)
|
||||||
builder.create(_VENV_DIR)
|
|
||||||
|
subprocess.check_call([python_311_exe, "-m", "venv", str(_VENV_DIR), "--symlinks"])
|
||||||
|
|
||||||
# Set environment variables
|
# Set environment variables
|
||||||
env = {}
|
env = {}
|
||||||
@@ -28,17 +67,16 @@ if args.makeflags:
|
|||||||
env["MAKEFLAGS"] = args.makeflags
|
env["MAKEFLAGS"] = args.makeflags
|
||||||
|
|
||||||
# Upgrade dependencies
|
# Upgrade dependencies
|
||||||
pip = [context.env_exe, "-m", "pip"]
|
pip = [str(_VENV_DIR / "bin" / "python"), "-m", "pip"]
|
||||||
subprocess.check_call(pip + ["install", "--upgrade", "pip"])
|
subprocess.check_call(pip + ["install", "--upgrade", "pip"])
|
||||||
subprocess.check_call(pip + ["install", "--upgrade", "setuptools", "wheel"])
|
subprocess.check_call(pip + ["install", "--upgrade", "setuptools", "wheel"])
|
||||||
|
|
||||||
# Install requirements
|
# Install requirements
|
||||||
subprocess.check_call(pip + ["install", "-e", str(_PROGRAM_DIR)])
|
subprocess.check_call(pip + ["install", "-e", str(_PROGRAM_DIR)])
|
||||||
|
|
||||||
|
|
||||||
# Install dev requirements
|
# Install dev requirements
|
||||||
if args.dev:
|
if args.dev:
|
||||||
subprocess.check_call(
|
subprocess.check_call(
|
||||||
pip + ["install", "-e", f"{_PROGRAM_DIR}[dev]"],
|
pip + ["install", "-e", f"{_PROGRAM_DIR}[dev]"],
|
||||||
env={**dict(os.environ), **env}
|
env={**dict(os.environ), **env}
|
||||||
)
|
)
|
||||||
Reference in new issue
Block a user