Integrate rvctl with rv32i and host debug profiles
This commit is contained in:
@@ -49,8 +49,9 @@ jest w osobnym repo `edu-workspace/workspace-info`.
|
||||
|
||||
3. Uruchamianie środowiska programistycznego w kontenerach
|
||||
|
||||
Dla wybranej karty `rvctl` uruchamia sesję tmux i kontener z przygotowanym
|
||||
środowiskiem developerskim. Docelowy model namespace `containers` jest w
|
||||
Dla wybranej karty i zadania `rvctl` uruchamia osobne profile kontenerów:
|
||||
`rv32i` dla Hazard3/RISC-V oraz `host` dla natywnego debugowania C. Oba
|
||||
profile opierają się na `nvim`, `tmux` i debuggerze. Model komend jest w
|
||||
`doc/containers.md`.
|
||||
|
||||
## Model katalogów
|
||||
|
||||
+97
-100
@@ -1,156 +1,153 @@
|
||||
# Containers
|
||||
# Kontenery i debug
|
||||
|
||||
Ten dokument opisuje docelowy model komend `rvctl` do uruchamiania
|
||||
środowiska programistycznego w kontenerach.
|
||||
`rvctl` integruje workspace z narzędziem `rv32i-hazard3-student-env`.
|
||||
Środowisko ma dwa profile kontenerów:
|
||||
|
||||
## Zasada
|
||||
- `rv32i` - docelowe środowisko RISC-V/Hazard3 z `nvim`, `tmux`,
|
||||
`gdb-multiarch`, toolchainem RISC-V i symulatorem Hazard3.
|
||||
- `host` - natywne środowisko na architekturze hosta z `clang`, `gcc`, `gdb`,
|
||||
opcjonalnie `lldb`, `valgrind`, `nvim` i `tmux`.
|
||||
|
||||
Kontener jest środowiskiem pracy dla wybranej karty, opcjonalnie zawężonym do
|
||||
konkretnego zadania. `rvctl` uruchamia je przez sesję `tmux`, tak aby terminal
|
||||
z kontenerem był łatwy do ponownego podłączenia.
|
||||
|
||||
Używamy namespace `containers`, a nie `container`, bo jest spójny z pluralnymi
|
||||
namespace'ami `tokens`, `cards` i `tasks`.
|
||||
Profile są uruchamiane jako oddzielne usługi `docker compose`, więc zależności
|
||||
RISC-V i zależności natywnego debugowania nie mieszają się ze sobą.
|
||||
|
||||
## Szybki przepływ
|
||||
|
||||
Typowy przepływ pracy:
|
||||
|
||||
```bash
|
||||
./rvctl tokens compare
|
||||
./rvctl series cards show inf bss
|
||||
./rvctl series cards tasks list inf bss
|
||||
./rvctl containers show inf bss
|
||||
./rvctl containers start inf bss --session rv-inf-bss --attach
|
||||
./rvctl containers list
|
||||
./rvctl containers stop rv-inf-bss
|
||||
./rvctl env list
|
||||
./rvctl env sync
|
||||
./rvctl env build rv32i
|
||||
./rvctl env build host
|
||||
|
||||
./rvctl debug rv32i inf bss 1
|
||||
./rvctl debug host inf bss 1
|
||||
./rvctl run host inf bss 1
|
||||
./rvctl shell rv32i inf bss
|
||||
./rvctl shell host inf bss
|
||||
```
|
||||
|
||||
## Model
|
||||
Jeżeli `workspace.json` ma ustawione domyślne wartości `defaults.series`,
|
||||
`defaults.card` i `defaults.task`, można używać krótszych komend:
|
||||
|
||||
Docelowo komendy `containers` pracują na trzech warstwach:
|
||||
```bash
|
||||
./rvctl debug rv32i
|
||||
./rvctl debug host
|
||||
./rvctl debug rv32i 4
|
||||
./rvctl debug host 4
|
||||
```
|
||||
|
||||
- karta pracy, np. `inf bss`
|
||||
- opcjonalne zadanie, np. `--task task1`
|
||||
- instancja środowiska, np. `--instance shell`
|
||||
## `env list`
|
||||
|
||||
Domyślne wartości pochodzą z `workspace.json`:
|
||||
Pokazuje dostępne profile i bieżący katalog narzędzia:
|
||||
|
||||
```bash
|
||||
./rvctl env list
|
||||
```
|
||||
|
||||
Typowy wynik:
|
||||
|
||||
```text
|
||||
defaults.series
|
||||
defaults.card
|
||||
defaults.task
|
||||
defaults.instance
|
||||
defaults.tmux_session
|
||||
defaults.tmux_window
|
||||
profile service image status purpose
|
||||
rv32i env edu-inf/rv32i-hazard3-env:latest ready RV32I/Hazard3 nvim + gdb-multiarch
|
||||
host host edu-inf/rv32i-hazard3-host-env:latest ready native host clang/gcc + gdb/lldb
|
||||
```
|
||||
|
||||
## Komendy
|
||||
## `env sync`
|
||||
|
||||
### `containers show [SERIES] [CARD]`
|
||||
|
||||
Pokazuje plan uruchomienia środowiska bez tworzenia sesji `tmux`.
|
||||
|
||||
```bash
|
||||
./rvctl containers show inf bss
|
||||
./rvctl containers show inf/bss --task task1
|
||||
```
|
||||
|
||||
Typowe pola:
|
||||
Klonuje albo aktualizuje repo środowiska do:
|
||||
|
||||
```text
|
||||
selector inf/bss
|
||||
task task1
|
||||
card_path ~/dev/workspace/rv/series/inf/lab-rv32i-strlen-bss-data-stack
|
||||
task_path ~/dev/workspace/rv/series/inf/lab-rv32i-strlen-bss-data-stack/task1
|
||||
tools_root ~/dev/workspace/rv/tools/rv32i-hazard3-env
|
||||
session rv-inf-bss
|
||||
window rv
|
||||
instance shell
|
||||
~/dev/workspace/rv/tools/rv32i-hazard3-student-env
|
||||
```
|
||||
|
||||
Ta komenda jest odpowiednikiem obecnego trybu:
|
||||
Komenda:
|
||||
|
||||
```bash
|
||||
./rvctl tmux-container inf bss --dry-run
|
||||
./rvctl env sync
|
||||
./rvctl env sync --dry-run
|
||||
```
|
||||
|
||||
### `containers start [SERIES] [CARD]`
|
||||
Jeżeli tego repo jeszcze nie ma w workspace, `rvctl` używa fallbacku z
|
||||
`~/dev/edu/repos/rv/rv32i-hazard3-env`, o ile jest dostępny.
|
||||
|
||||
Uruchamia sesję `tmux` i kontener dla wybranej karty.
|
||||
## `env build`
|
||||
|
||||
Buduje wybrany profil kontenera:
|
||||
|
||||
```bash
|
||||
./rvctl containers start inf bss
|
||||
./rvctl containers start inf/bss --session rv-inf-bss
|
||||
./rvctl containers start inf bss --task task1 --instance shell --attach
|
||||
./rvctl env build rv32i
|
||||
./rvctl env build host
|
||||
```
|
||||
|
||||
Przełączniki:
|
||||
|
||||
- `--task TASK`
|
||||
Wybiera zadanie w karcie. Bez tego `rvctl` używa domyślnego zadania z
|
||||
`workspace.json` albo pracuje na katalogu karty.
|
||||
- `--session NAME`
|
||||
Nadpisuje nazwę sesji `tmux`.
|
||||
- `--window NAME`
|
||||
Nadpisuje nazwę okna `tmux`.
|
||||
- `--instance NAME`
|
||||
Ustawia wariant środowiska, np. `shell`.
|
||||
- `--attach`
|
||||
Po uruchomieniu od razu podłącza terminal do sesji.
|
||||
|
||||
### `containers list`
|
||||
|
||||
Listuje aktywne środowiska uruchomione przez `rvctl`.
|
||||
Wariant `--dry-run` pokazuje dokładną komendę bez uruchamiania Dockera:
|
||||
|
||||
```bash
|
||||
./rvctl containers list
|
||||
./rvctl env build host --dry-run
|
||||
```
|
||||
|
||||
Przykładowy wynik:
|
||||
## `debug`
|
||||
|
||||
```text
|
||||
session selector task instance window
|
||||
-------- -------- ----- -------- ------
|
||||
rv-inf-bss inf/bss task1 shell rv
|
||||
```
|
||||
|
||||
### `containers attach SESSION`
|
||||
|
||||
Podłącza terminal do istniejącej sesji.
|
||||
Uruchamia debugowanie zadania w wybranym profilu.
|
||||
|
||||
```bash
|
||||
./rvctl containers attach rv-inf-bss
|
||||
./rvctl debug rv32i inf bss 1
|
||||
./rvctl debug host inf bss 1
|
||||
```
|
||||
|
||||
Technicznie odpowiada to:
|
||||
W profilu `rv32i` startuje środowisko Hazard3: symulator, `gdb-multiarch` i
|
||||
`nvim` w sesji `tmux`.
|
||||
|
||||
W profilu `host` wybrane źródło C jest kompilowane natywnie z debug info, a
|
||||
potem uruchamiane w `gdb` obok `nvim`.
|
||||
Ten profil jest przeznaczony dla tasków C z funkcją `main`; taski czysto
|
||||
asemblerskie nadal debugujemy w profilu `rv32i`.
|
||||
|
||||
Przydatne warianty:
|
||||
|
||||
```bash
|
||||
tmux attach -t rv-inf-bss
|
||||
./rvctl debug rv32i bss 4
|
||||
./rvctl debug host bss 4
|
||||
./rvctl debug host 4 --editor vim
|
||||
./rvctl debug host 4 --instance host-bss-t4
|
||||
./rvctl debug host 4 --dry-run
|
||||
```
|
||||
|
||||
### `containers stop SESSION`
|
||||
## `run`
|
||||
|
||||
Zamyka sesję `tmux`, a razem z nią uruchomiony w niej proces kontenera.
|
||||
Na razie `run` jest wdrożone dla profilu `host`. Buduje wybrany task natywnie i
|
||||
uruchamia wynikowy program:
|
||||
|
||||
```bash
|
||||
./rvctl containers stop rv-inf-bss
|
||||
./rvctl run host inf bss 1
|
||||
./rvctl run host 4
|
||||
```
|
||||
|
||||
Technicznie odpowiada to:
|
||||
Dla profilu `rv32i` używamy obecnie `debug rv32i`, bo przepływ RISC-V zakłada
|
||||
symulator Hazard3 i GDB.
|
||||
|
||||
## `shell`
|
||||
|
||||
Otwiera shell w wybranym profilu kontenera z podmontowaną kartą:
|
||||
|
||||
```bash
|
||||
tmux kill-session -t rv-inf-bss
|
||||
./rvctl shell rv32i inf bss
|
||||
./rvctl shell host inf bss
|
||||
```
|
||||
|
||||
## Aliasowanie Starej Komendy
|
||||
## Selektory
|
||||
|
||||
Stara komenda może zostać jako alias kompatybilności:
|
||||
Komendy `debug`, `run` i `shell` używają tych samych skrótów co `tasks`:
|
||||
|
||||
```text
|
||||
tmux-container inf bss --dry-run -> containers show inf bss
|
||||
tmux-container inf bss --session NAME -> containers start inf bss --session NAME
|
||||
tmux-container inf/bss --attach -> containers start inf/bss --attach
|
||||
```bash
|
||||
./rvctl debug host 1
|
||||
./rvctl debug host bss 1
|
||||
./rvctl debug host inf bss 1
|
||||
```
|
||||
|
||||
Dokumentacja i nowe przykłady powinny promować namespace `containers`.
|
||||
`rvctl` rozwiązuje `1` do właściwego zadania, na przykład
|
||||
`task1_bss`, na podstawie plików w `src/tasks`.
|
||||
|
||||
## Starsza komenda
|
||||
|
||||
`tmux-container` zostaje jako alias kompatybilności dla starszego trybu
|
||||
shellowego. Nowe przykłady powinny używać `debug`, `run`, `shell` i `env`.
|
||||
|
||||
@@ -220,6 +220,12 @@ Komendy:
|
||||
- `series cards tasks list SERIES CARD`
|
||||
- `series cards tasks show SERIES CARD TASK`
|
||||
- `series cards tasks switch SERIES CARD TASK`
|
||||
- `env list`
|
||||
- `env sync`
|
||||
- `env build rv32i|host`
|
||||
- `debug rv32i|host [TASK|CARD TASK|SERIES CARD TASK]`
|
||||
- `run host [TASK|CARD TASK|SERIES CARD TASK]`
|
||||
- `shell rv32i|host [CARD|SERIES CARD]`
|
||||
- `tasks list [CARD|SERIES CARD]`
|
||||
- `tasks show TASK|CARD TASK|SERIES CARD TASK`
|
||||
- `tasks switch TASK|CARD TASK|SERIES CARD TASK`
|
||||
@@ -560,6 +566,84 @@ Ta komenda działa tak samo jak pełna forma:
|
||||
./rvctl series cards tasks switch inf bss 4
|
||||
```
|
||||
|
||||
## `env list`
|
||||
|
||||
Pokazuje profile środowiska kontenerowego obsługiwane przez `rvctl`.
|
||||
|
||||
```bash
|
||||
./rvctl env list
|
||||
```
|
||||
|
||||
Profile:
|
||||
|
||||
- `rv32i` - RISC-V/Hazard3, `nvim`, `tmux`, `gdb-multiarch`
|
||||
- `host` - natywny kontener z `clang`, `gcc`, `gdb`, opcjonalnie `lldb`
|
||||
|
||||
## `env sync`
|
||||
|
||||
Klonuje albo aktualizuje repo `rv32i-hazard3-student-env` do katalogu
|
||||
`~/dev/workspace/rv/tools/rv32i-hazard3-student-env`.
|
||||
|
||||
```bash
|
||||
./rvctl env sync
|
||||
./rvctl env sync --dry-run
|
||||
```
|
||||
|
||||
## `env build rv32i|host`
|
||||
|
||||
Buduje wybrany profil `docker compose`.
|
||||
|
||||
```bash
|
||||
./rvctl env build rv32i
|
||||
./rvctl env build host
|
||||
./rvctl env build host --dry-run
|
||||
```
|
||||
|
||||
## `debug rv32i|host`
|
||||
|
||||
Uruchamia debugowanie zadania w kontenerze. Selektor może być pełny albo krótki.
|
||||
|
||||
```bash
|
||||
./rvctl debug rv32i inf bss 1
|
||||
./rvctl debug host inf bss 1
|
||||
./rvctl debug rv32i 4
|
||||
./rvctl debug host 4
|
||||
```
|
||||
|
||||
`rv32i` uruchamia symulator Hazard3, `gdb-multiarch` i `nvim`. `host` kompiluje
|
||||
zadanie natywnie i otwiera `gdb` obok `nvim`. Profil `host` jest przeznaczony
|
||||
dla tasków C z funkcją `main`; taski czysto asemblerowe debugujemy w profilu
|
||||
`rv32i`.
|
||||
|
||||
Przełączniki:
|
||||
|
||||
- `--task TASK` - nadpisuje zadanie z selektora albo z `workspace.json`
|
||||
- `--instance NAME` - ustawia nazwę instancji kontenera/tmuxa
|
||||
- `--editor nvim|vim` - wybiera edytor w kontenerze
|
||||
- `--dry-run` - pokazuje komendę bez uruchamiania Dockera
|
||||
|
||||
## `run host`
|
||||
|
||||
Buduje i uruchamia wybrane zadanie natywnie w kontenerze `host`.
|
||||
|
||||
```bash
|
||||
./rvctl run host inf bss 1
|
||||
./rvctl run host 4
|
||||
```
|
||||
|
||||
Profil `rv32i` pracuje obecnie przez `debug rv32i`, bo wymaga symulatora
|
||||
Hazard3 i połączenia GDB.
|
||||
|
||||
## `shell rv32i|host`
|
||||
|
||||
Otwiera shell w wybranym profilu kontenera z podmontowaną kartą.
|
||||
|
||||
```bash
|
||||
./rvctl shell rv32i inf bss
|
||||
./rvctl shell host inf bss
|
||||
./rvctl shell host --dry-run
|
||||
```
|
||||
|
||||
## `list-series`
|
||||
|
||||
Alias kompatybilności dla `series list`.
|
||||
|
||||
@@ -52,6 +52,7 @@ class WorkspaceConfig:
|
||||
token_path: Path
|
||||
tools_root_candidates: list[Path]
|
||||
tools_root: Path
|
||||
env_tool_url: str
|
||||
defaults: dict[str, str]
|
||||
git: GitConfig
|
||||
|
||||
@@ -109,6 +110,19 @@ REPO_PERMISSION_FIELDS = [
|
||||
("r", "read"),
|
||||
]
|
||||
|
||||
ENV_PROFILES = {
|
||||
"rv32i": {
|
||||
"service": "env",
|
||||
"image": "edu-inf/rv32i-hazard3-env:latest",
|
||||
"purpose": "RV32I/Hazard3 nvim + gdb-multiarch",
|
||||
},
|
||||
"host": {
|
||||
"service": "host",
|
||||
"image": "edu-inf/rv32i-hazard3-host-env:latest",
|
||||
"purpose": "native host clang/gcc + gdb/lldb",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def load_config(config_path: Path) -> WorkspaceConfig:
|
||||
raw = json.loads(config_path.read_text(encoding="utf-8"))
|
||||
@@ -116,6 +130,8 @@ def load_config(config_path: Path) -> WorkspaceConfig:
|
||||
|
||||
original_root = expand_path(raw.get("original_root", "~/dev/edu/repos/rv"), base_dir)
|
||||
workspace_root = expand_path(raw.get("workspace_root", "~/dev/workspace/rv"), base_dir)
|
||||
git_raw = raw.get("git", {})
|
||||
base_url = git_raw.get("base_url", "http://77.90.8.171:3001").rstrip("/")
|
||||
original_series_root = expand_path(raw.get("original_series_root", str(original_root / "series")), base_dir)
|
||||
series_root = expand_path(raw.get("series_root", str(workspace_root / "series")), base_dir)
|
||||
socket_root = expand_path(raw.get("socket_root", str(workspace_root / "sockets")), base_dir)
|
||||
@@ -128,15 +144,16 @@ def load_config(config_path: Path) -> WorkspaceConfig:
|
||||
candidate_values = raw.get("tools_root_candidates")
|
||||
if not candidate_values:
|
||||
candidate_values = [
|
||||
raw.get("tools_root", str(workspace_root / "tools" / "rv32i-hazard3-env")),
|
||||
raw.get("tools_root", str(workspace_root / "tools" / "rv32i-hazard3-student-env")),
|
||||
str(workspace_root / "tools" / "rv32i-hazard3-env"),
|
||||
str(original_root / "rv32i-hazard3-student-env"),
|
||||
str(original_root / "rv32i-hazard3-env"),
|
||||
]
|
||||
tools_root_candidates = [expand_path(value, base_dir) for value in candidate_values]
|
||||
|
||||
tools_root = next((path for path in tools_root_candidates if path.exists()), tools_root_candidates[0])
|
||||
env_tool_url = raw.get("env_tool_url", f"{base_url}/edu-inf/rv32i-hazard3-student-env.git")
|
||||
defaults = raw.get("defaults", {})
|
||||
git_raw = raw.get("git", {})
|
||||
base_url = git_raw.get("base_url", "http://77.90.8.171:3001").rstrip("/")
|
||||
workspace_info_url = raw.get("workspace_info_url", f"{base_url}/edu-workspace/workspace-info.git")
|
||||
git = GitConfig(
|
||||
base_url=base_url,
|
||||
@@ -162,6 +179,7 @@ def load_config(config_path: Path) -> WorkspaceConfig:
|
||||
token_path=token_path,
|
||||
tools_root_candidates=tools_root_candidates,
|
||||
tools_root=tools_root,
|
||||
env_tool_url=env_tool_url,
|
||||
defaults=defaults,
|
||||
git=git,
|
||||
)
|
||||
@@ -3316,6 +3334,251 @@ def run_tmux_container(config: WorkspaceConfig, args: argparse.Namespace) -> Non
|
||||
print(f"Attach with: tmux attach -t {session_name}")
|
||||
|
||||
|
||||
def normalize_env_profile(profile: str) -> str:
|
||||
aliases = {
|
||||
"riscv": "rv32i",
|
||||
"riscv32": "rv32i",
|
||||
"native": "host",
|
||||
}
|
||||
normalized = aliases.get(profile, profile)
|
||||
if normalized not in ENV_PROFILES:
|
||||
available = ", ".join(sorted(ENV_PROFILES))
|
||||
raise SystemExit(f"Unsupported environment profile '{profile}'. Available: {available}")
|
||||
return normalized
|
||||
|
||||
|
||||
def env_tool_command_path(config: WorkspaceConfig) -> Path:
|
||||
return config.tools_root / "rv"
|
||||
|
||||
|
||||
def ensure_env_tool(config: WorkspaceConfig) -> Path:
|
||||
rv_script = env_tool_command_path(config)
|
||||
if rv_script.is_file():
|
||||
return rv_script
|
||||
candidates = "\n".join(f" - {path}" for path in config.tools_root_candidates)
|
||||
raise SystemExit(
|
||||
"Missing rv32i-hazard3-student-env launcher script 'rv'.\n"
|
||||
f"Checked:\n{candidates}\n"
|
||||
"Run: ./rvctl env sync"
|
||||
)
|
||||
|
||||
|
||||
def env_sync_target(config: WorkspaceConfig) -> Path:
|
||||
return config.workspace_root / "tools" / "rv32i-hazard3-student-env"
|
||||
|
||||
|
||||
def sync_env_tool(config: WorkspaceConfig, dry_run: bool = False) -> dict[str, str]:
|
||||
target = env_sync_target(config)
|
||||
if dry_run:
|
||||
command = "pull" if (target / ".git").is_dir() else "clone"
|
||||
return {
|
||||
"env_tool_path": str(target),
|
||||
"env_tool_url": config.env_tool_url,
|
||||
"command": command,
|
||||
"status": "dry-run",
|
||||
}
|
||||
target.parent.mkdir(parents=True, exist_ok=True)
|
||||
if (target / ".git").is_dir():
|
||||
subprocess.run(["git", "-C", str(target), "pull", "--ff-only"], check=True)
|
||||
config.tools_root = target
|
||||
return {
|
||||
"env_tool_path": str(target),
|
||||
"env_tool_url": config.env_tool_url,
|
||||
"command": "pull",
|
||||
"status": "ready",
|
||||
}
|
||||
if target.exists():
|
||||
raise SystemExit(f"Environment tool path exists but is not a git repo: {target}")
|
||||
subprocess.run(["git", "clone", config.env_tool_url, str(target)], check=True)
|
||||
config.tools_root = target
|
||||
return {
|
||||
"env_tool_path": str(target),
|
||||
"env_tool_url": config.env_tool_url,
|
||||
"command": "clone",
|
||||
"status": "ready",
|
||||
}
|
||||
|
||||
|
||||
def task_selector_like(value: str) -> bool:
|
||||
return bool(re.fullmatch(r"(?i)(task[-_]?)?\d+.*|t\d+", value.strip()))
|
||||
|
||||
|
||||
def resolve_env_target(
|
||||
config: WorkspaceConfig,
|
||||
values: list[str],
|
||||
task_override: str | None = None,
|
||||
) -> tuple[str, str, str]:
|
||||
if len(values) > 3:
|
||||
raise SystemExit("Pass '<task>', '<card> [task]' or '<series> <card> [task]'.")
|
||||
|
||||
if not values:
|
||||
series_name, card_name = resolve_selector(config, None, None)
|
||||
card_path = resolve_workspace_card_path(config, series_name, card_name)
|
||||
task_name = resolve_submission_task(config, card_path, task_override)
|
||||
return series_name, card_name, task_name
|
||||
|
||||
if len(values) == 1:
|
||||
if task_selector_like(values[0]):
|
||||
series_name, card_name = resolve_selector(config, None, None)
|
||||
task_selector = task_override or values[0]
|
||||
else:
|
||||
series_name, card_name = resolve_selector(config, values[0], None)
|
||||
task_selector = task_override
|
||||
card_path = resolve_workspace_card_path(config, series_name, card_name)
|
||||
task_name = resolve_submission_task(config, card_path, task_selector)
|
||||
return series_name, card_name, task_name
|
||||
|
||||
if len(values) == 2:
|
||||
if task_selector_like(values[1]):
|
||||
series_name, card_name = resolve_selector(config, values[0], None)
|
||||
task_selector = task_override or values[1]
|
||||
else:
|
||||
series_name, card_name = resolve_selector(config, values[0], values[1])
|
||||
task_selector = task_override
|
||||
card_path = resolve_workspace_card_path(config, series_name, card_name)
|
||||
task_name = resolve_submission_task(config, card_path, task_selector)
|
||||
return series_name, card_name, task_name
|
||||
|
||||
series_name, card_name = resolve_selector(config, values[0], values[1])
|
||||
card_path = resolve_workspace_card_path(config, series_name, card_name)
|
||||
task_name = resolve_submission_task(config, card_path, task_override or values[2])
|
||||
return series_name, card_name, task_name
|
||||
|
||||
|
||||
def default_env_instance(profile: str, series_name: str, card_name: str, task_name: str, action: str) -> str:
|
||||
task_part = task_branch_part(task_name).lower()
|
||||
return slug_value(f"{profile}-{series_name}-{card_name}-{task_part}-{action}", "environment instance")
|
||||
|
||||
|
||||
def env_action_command(
|
||||
config: WorkspaceConfig,
|
||||
profile: str,
|
||||
action: str,
|
||||
series_name: str | None = None,
|
||||
card_name: str | None = None,
|
||||
task_name: str | None = None,
|
||||
instance: str | None = None,
|
||||
editor: str | None = None,
|
||||
) -> tuple[str, dict[str, str]]:
|
||||
profile = normalize_env_profile(profile)
|
||||
rv_script = ensure_env_tool(config)
|
||||
env_vars = {
|
||||
"WORKSPACE_ROOT": str(config.workspace_root),
|
||||
"RV_CARDS_ROOT": str(config.series_root),
|
||||
"RV_PROFILE": profile,
|
||||
}
|
||||
command_args = ["bash", "./rv"]
|
||||
|
||||
card_path: Path | None = None
|
||||
selector = ""
|
||||
if series_name and card_name:
|
||||
card_path = resolve_workspace_card_path(config, series_name, card_name)
|
||||
selector = f"{series_name}/{card_name}"
|
||||
env_vars.update(
|
||||
{
|
||||
"RV_REPO_PATH": str(card_path),
|
||||
"RV_REPO_HOST": str(card_path),
|
||||
"RV_CARD": card_path.name,
|
||||
}
|
||||
)
|
||||
|
||||
if instance:
|
||||
env_vars["RV_INSTANCE"] = instance
|
||||
elif series_name and card_name and task_name:
|
||||
env_vars["RV_INSTANCE"] = default_env_instance(profile, series_name, card_name, task_name, action)
|
||||
|
||||
if editor:
|
||||
env_vars["RV_EDITOR"] = editor
|
||||
|
||||
if action == "build":
|
||||
command_args.append("build")
|
||||
elif action == "shell":
|
||||
command_args.append("shell")
|
||||
elif action == "debug":
|
||||
if not task_name:
|
||||
raise SystemExit("debug requires a task selector.")
|
||||
if profile == "rv32i":
|
||||
command_args.extend(["nvim", task_name])
|
||||
else:
|
||||
command_args.extend(["debug", task_name])
|
||||
elif action == "run":
|
||||
if not task_name:
|
||||
raise SystemExit("run requires a task selector.")
|
||||
if profile == "rv32i":
|
||||
raise SystemExit("rv32i run is not separate yet. Use: ./rvctl debug rv32i ...")
|
||||
command_args.extend(["run", task_name])
|
||||
else:
|
||||
raise SystemExit(f"Unsupported environment action: {action}")
|
||||
|
||||
env_prefix = " ".join(f"{key}={shlex.quote(value)}" for key, value in env_vars.items())
|
||||
shell_command = " && ".join(
|
||||
[
|
||||
f"cd {shlex.quote(str(rv_script.parent))}",
|
||||
" ".join([env_prefix, *[shlex.quote(part) for part in command_args]]),
|
||||
]
|
||||
)
|
||||
details = {
|
||||
"profile": profile,
|
||||
"service": ENV_PROFILES[profile]["service"],
|
||||
"selector": selector,
|
||||
"task": task_name or "",
|
||||
"card_path": str(card_path or ""),
|
||||
"tools_root": str(config.tools_root),
|
||||
"instance": env_vars.get("RV_INSTANCE", ""),
|
||||
"command": shell_command,
|
||||
}
|
||||
return shell_command, details
|
||||
|
||||
|
||||
def print_env_plan(details: dict[str, str]) -> None:
|
||||
for key in ["profile", "service", "selector", "task", "card_path", "tools_root", "instance", "command"]:
|
||||
print(f"{key}\t{details.get(key, '')}")
|
||||
|
||||
|
||||
def run_env(config: WorkspaceConfig, args: argparse.Namespace) -> None:
|
||||
if args.env_command == "list":
|
||||
print("profile\tservice\timage\tstatus\tpurpose")
|
||||
for profile, info in ENV_PROFILES.items():
|
||||
print(
|
||||
f"{profile}\t{info['service']}\t{info['image']}\t"
|
||||
f"{'ready' if env_tool_command_path(config).is_file() else 'missing'}\t{info['purpose']}"
|
||||
)
|
||||
print(f"tools_root\t{config.tools_root}")
|
||||
print(f"env_tool_url\t{config.env_tool_url}")
|
||||
return
|
||||
if args.env_command == "sync":
|
||||
print_key_values(sync_env_tool(config, dry_run=args.dry_run))
|
||||
return
|
||||
if args.env_command == "build":
|
||||
profile = normalize_env_profile(args.profile)
|
||||
shell_command, details = env_action_command(config, profile, "build")
|
||||
if args.dry_run:
|
||||
print_env_plan(details)
|
||||
return
|
||||
subprocess.run(["bash", "-lc", shell_command], check=True)
|
||||
return
|
||||
raise SystemExit(f"Unsupported env command: {args.env_command}")
|
||||
|
||||
|
||||
def run_profile_action(config: WorkspaceConfig, args: argparse.Namespace, action: str) -> None:
|
||||
profile = normalize_env_profile(args.profile)
|
||||
series_name, card_name, task_name = resolve_env_target(config, args.selector, getattr(args, "task", None))
|
||||
shell_command, details = env_action_command(
|
||||
config,
|
||||
profile,
|
||||
action,
|
||||
series_name=series_name,
|
||||
card_name=card_name,
|
||||
task_name=task_name,
|
||||
instance=getattr(args, "instance", None),
|
||||
editor=getattr(args, "editor", None),
|
||||
)
|
||||
if args.dry_run:
|
||||
print_env_plan(details)
|
||||
return
|
||||
subprocess.run(["bash", "-lc", shell_command], check=True)
|
||||
|
||||
|
||||
def workspace_info_status(config: WorkspaceConfig) -> str:
|
||||
info_root = workspace_info_root(config)
|
||||
if (info_root / ".git").is_dir():
|
||||
@@ -3395,6 +3658,7 @@ def print_config(config: WorkspaceConfig) -> None:
|
||||
print(f"socket_root\t{config.socket_root}")
|
||||
print(f"token_path\t{config.token_path}")
|
||||
print(f"tools_root\t{config.tools_root}")
|
||||
print(f"env_tool_url\t{config.env_tool_url}")
|
||||
print(f"git_base_url\t{config.git.base_url}")
|
||||
print(f"git_source_org\t{config.git.source_org}")
|
||||
print(f"git_answer_org\t{config.git.answer_org}")
|
||||
@@ -3474,6 +3738,10 @@ def print_main_overview(config_path: Path) -> None:
|
||||
["workspace", "show|sync", "show or update meta/workspace-info"],
|
||||
["series", "list|cards|fetch|tasks", "work with series, cards and tasks"],
|
||||
["tasks", "list|show|switch", "shortcut for tasks in the default card"],
|
||||
["env", "list|sync|build", "manage rv32i/host container environment"],
|
||||
["debug", "rv32i|host [selector]", "open nvim + gdb in the selected container profile"],
|
||||
["run", "host [selector]", "build and run selected task natively in host container"],
|
||||
["shell", "rv32i|host [selector]", "open a shell in the selected container profile"],
|
||||
["list-series", "", "compat alias for series list"],
|
||||
["list-cards", "[series]", "compat alias for series cards list"],
|
||||
["tmux-container", "[series] [card]", "start tmux with container in pane 0"],
|
||||
@@ -3493,6 +3761,8 @@ def print_main_overview(config_path: Path) -> None:
|
||||
["5", "./rvctl series cards fetch inf bss", "fetch card and prepare r1a"],
|
||||
["6", "./rvctl tasks list", "list tasks from the default card"],
|
||||
["7", "./rvctl tasks switch 4", "switch to the answer branch for task 4"],
|
||||
["8", "./rvctl debug rv32i 4", "debug task 4 in RV32I/Hazard3"],
|
||||
["9", "./rvctl debug host 4", "debug task 4 natively in a host container"],
|
||||
],
|
||||
)
|
||||
print()
|
||||
@@ -3502,6 +3772,7 @@ def print_main_overview(config_path: Path) -> None:
|
||||
print(" doc/rvctl.md")
|
||||
print(" doc/tokens.md")
|
||||
print(" doc/tokens.schema.json")
|
||||
print(" doc/containers.md")
|
||||
print(" doc/workspace.md")
|
||||
|
||||
|
||||
@@ -3598,6 +3869,37 @@ def build_parser() -> argparse.ArgumentParser:
|
||||
workspace_sync_parser = workspace_subparsers.add_parser("sync", help="Clone or update workspace-info into meta/.")
|
||||
workspace_sync_parser.add_argument("--dry-run", action="store_true", help="Print plan without cloning or pulling.")
|
||||
|
||||
env_parser = subparsers.add_parser("env", help="Manage rv32i/host container environments.")
|
||||
env_subparsers = env_parser.add_subparsers(dest="env_command", required=True)
|
||||
env_subparsers.add_parser("list", help="List supported environment profiles.")
|
||||
env_sync_parser = env_subparsers.add_parser("sync", help="Clone or update rv32i-hazard3-student-env into tools/.")
|
||||
env_sync_parser.add_argument("--dry-run", action="store_true", help="Print plan without cloning or pulling.")
|
||||
env_build_parser = env_subparsers.add_parser("build", help="Build one container profile.")
|
||||
env_build_parser.add_argument("profile", choices=sorted(ENV_PROFILES), help="Environment profile.")
|
||||
env_build_parser.add_argument("--dry-run", action="store_true", help="Print command without building.")
|
||||
|
||||
debug_parser = subparsers.add_parser("debug", help="Open selected task in nvim + debugger inside a container profile.")
|
||||
debug_parser.add_argument("profile", choices=sorted(ENV_PROFILES), help="Environment profile: rv32i or host.")
|
||||
debug_parser.add_argument("selector", nargs="*", help="Optional selector: [task], [card task] or [series card task].")
|
||||
debug_parser.add_argument("--task", help="Task override. Defaults to selector or defaults.task.")
|
||||
debug_parser.add_argument("--instance", help="Container/tmux instance name.")
|
||||
debug_parser.add_argument("--editor", choices=["nvim", "vim"], help="Editor inside the container.")
|
||||
debug_parser.add_argument("--dry-run", action="store_true", help="Print command without starting Docker.")
|
||||
|
||||
run_parser = subparsers.add_parser("run", help="Build and run selected task inside a container profile.")
|
||||
run_parser.add_argument("profile", choices=sorted(ENV_PROFILES), help="Environment profile. Currently host supports run.")
|
||||
run_parser.add_argument("selector", nargs="*", help="Optional selector: [task], [card task] or [series card task].")
|
||||
run_parser.add_argument("--task", help="Task override. Defaults to selector or defaults.task.")
|
||||
run_parser.add_argument("--instance", help="Container instance name.")
|
||||
run_parser.add_argument("--dry-run", action="store_true", help="Print command without starting Docker.")
|
||||
|
||||
shell_parser = subparsers.add_parser("shell", help="Open a shell inside a container profile.")
|
||||
shell_parser.add_argument("profile", choices=sorted(ENV_PROFILES), help="Environment profile: rv32i or host.")
|
||||
shell_parser.add_argument("selector", nargs="*", help="Optional selector: [card] or [series card].")
|
||||
shell_parser.add_argument("--task", help="Task used only for instance naming.")
|
||||
shell_parser.add_argument("--instance", help="Container instance name.")
|
||||
shell_parser.add_argument("--dry-run", action="store_true", help="Print command without starting Docker.")
|
||||
|
||||
tasks_parser = subparsers.add_parser("tasks", help="Shortcut for tasks in the default card.")
|
||||
tasks_subparsers = tasks_parser.add_subparsers(dest="tasks_command", required=True)
|
||||
|
||||
@@ -3836,7 +4138,7 @@ def main() -> int:
|
||||
parser = build_parser()
|
||||
args = parser.parse_args()
|
||||
config = load_config(args.config)
|
||||
if args.command in {"list-series", "list-cards", "series", "tasks", "tmux-container", "submission"}:
|
||||
if args.command in {"list-series", "list-cards", "series", "tasks", "tmux-container", "submission", "debug", "run", "shell"}:
|
||||
ensure_workspace_info(config)
|
||||
|
||||
if args.command == "show-config":
|
||||
@@ -3851,6 +4153,18 @@ def main() -> int:
|
||||
if args.command == "workspace":
|
||||
run_workspace(config, args)
|
||||
return 0
|
||||
if args.command == "env":
|
||||
run_env(config, args)
|
||||
return 0
|
||||
if args.command == "debug":
|
||||
run_profile_action(config, args, "debug")
|
||||
return 0
|
||||
if args.command == "run":
|
||||
run_profile_action(config, args, "run")
|
||||
return 0
|
||||
if args.command == "shell":
|
||||
run_profile_action(config, args, "shell")
|
||||
return 0
|
||||
if args.command == "tasks":
|
||||
run_tasks(config, args)
|
||||
return 0
|
||||
|
||||
@@ -8,8 +8,11 @@
|
||||
"series_root": "~/dev/workspace/rv/series",
|
||||
"socket_root": "~/dev/workspace/rv/sockets",
|
||||
"token_file": "~/dev/workspace/rv/tokens/tokens.json",
|
||||
"env_tool_url": "http://77.90.8.171:3001/edu-inf/rv32i-hazard3-student-env.git",
|
||||
"tools_root_candidates": [
|
||||
"~/dev/workspace/rv/tools/rv32i-hazard3-student-env",
|
||||
"~/dev/workspace/rv/tools/rv32i-hazard3-env",
|
||||
"~/dev/edu/repos/rv/rv32i-hazard3-student-env",
|
||||
"~/dev/edu/repos/rv/rv32i-hazard3-env"
|
||||
],
|
||||
"git": {
|
||||
|
||||
Reference in New Issue
Block a user