import argparse
from collections import OrderedDict
from collections.abc import Mapping
from dataclasses import dataclass
import json
from logging import getLogger
from pathlib import Path
from typing import Any
from jitx.run import DesignFactory, DesignFinder
LAUNCH_JSON_FILENAME = "launch.json"
TASKS_JSON_FILENAME = "tasks.json"
RUN_MODULE = "jitx"
BUILD_COMMAND = "build"
TASK_GROUP = {"kind": "build"}
JITX_TASK_DETAIL = "JITX Build task"
RUN_ALL_LABEL = "RunAll JITX Designs in Project"
logger = getLogger("jitx.launch_config")
[docs]
@dataclass
class VSCodePaths:
vscode_dir: Path
launch_json: Path
tasks_json: Path
[docs]
def get_vscode_paths(project_path) -> VSCodePaths:
vscode_dir = Path(project_path).resolve() / ".vscode"
return VSCodePaths(
vscode_dir=vscode_dir,
launch_json=vscode_dir / LAUNCH_JSON_FILENAME,
tasks_json=vscode_dir / TASKS_JSON_FILENAME,
)
[docs]
def get_designs(project_path):
designs = []
def collect(ob: Mapping[str, Any] | str, file=None):
if isinstance(ob, Mapping):
designs.extend(ob.get("designs", ()))
DesignFactory(DesignFinder([str(project_path)]), formatter=collect).list()
filtered = [d for d in designs if d != "jitx.sample.SampleDesign"]
return sorted(filtered)
[docs]
def load_existing_json(path):
if path.exists():
try:
with path.open("r", encoding="utf-8") as f:
return json.load(f)
except json.JSONDecodeError:
logger.warning(f"Invalid JSON in {path}. Resetting file.")
config_key = "configurations" if "launch" in str(path) else "tasks"
return {"version": "2.0.0", config_key: []}
[docs]
def make_args(design=None):
base = [
"--port",
"${command:jitx.get-websocket-port}",
]
return [BUILD_COMMAND, design, *base] if design else ["build-all", *base]
[docs]
def make_launch_config(name, args):
return OrderedDict(
[
("name", name),
("type", "debugpy"),
("request", "launch"),
("console", "integratedTerminal"),
("module", RUN_MODULE),
("args", args),
("justMyCode", False),
]
)
[docs]
def update_launch_json(paths, designs):
launch_data = load_existing_json(paths.launch_json)
existing_configs = launch_data.get("configurations", [])
non_jitx_configs = [
cfg for cfg in existing_configs if cfg.get("module") != RUN_MODULE
]
run_all_config = next(
(cfg for cfg in existing_configs if cfg.get("name") == RUN_ALL_LABEL), None
)
if not run_all_config:
run_all_config = make_launch_config(RUN_ALL_LABEL, make_args())
jitx_configs = [
make_launch_config(f"Run {design}", make_args(design)) for design in designs
]
launch_data["configurations"] = [*non_jitx_configs, run_all_config, *jitx_configs]
with paths.launch_json.open("w", encoding="utf-8") as f:
json.dump(launch_data, f, indent=4)
logger.info(f"Updated {paths.launch_json}")
[docs]
def make_task(design_label, args):
return {
"label": f"Run {design_label}",
"type": "process",
"command": "python",
"args": ["-m", RUN_MODULE] + args,
"problemMatcher": [],
"group": TASK_GROUP,
"detail": JITX_TASK_DETAIL,
}
[docs]
def create_tasks_json(paths, designs):
tasks_data = load_existing_json(paths.tasks_json)
existing_tasks = tasks_data.get("tasks", [])
retained_tasks = [
task for task in existing_tasks if task.get("detail") != JITX_TASK_DETAIL
]
jitx_tasks = [make_task(design, make_args(design)) for design in designs]
if designs:
jitx_tasks.append(make_task("All JITX Designs in Project", make_args()))
tasks_data["tasks"] = [*retained_tasks, *jitx_tasks]
tasks_data.pop("inputs", None)
with paths.tasks_json.open("w", encoding="utf-8") as f:
json.dump(tasks_data, f, indent=4)
logger.info(f"Updated {paths.tasks_json}")
[docs]
def generate_config(project_path="."):
"""Generate or update launch.json and tasks.json for a JITX project."""
project_path = Path(project_path).resolve()
if project_path.exists():
paths = get_vscode_paths(project_path)
paths.vscode_dir.mkdir(parents=True, exist_ok=True)
designs = get_designs(project_path)
update_launch_json(paths, designs)
create_tasks_json(paths, designs)
else:
logger.error(f"The specified project path does not exist: {project_path}")
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="Generate or update launch.json and tasks.json for a JITX project."
)
parser.add_argument(
"project_path", type=str, help="Path to the target project folder"
)
args = parser.parse_args()
generate_config(args.project_path)