diff --git a/.github/workflows/install-and-launch.yml b/.github/workflows/install-and-launch.yml index 398ce40..96c861d 100644 --- a/.github/workflows/install-and-launch.yml +++ b/.github/workflows/install-and-launch.yml @@ -98,138 +98,3 @@ jobs: exit 1 fi - windows-build-test: - name: Windows build & installer test (${{ matrix.arch }}) - strategy: - fail-fast: false - matrix: - include: - - arch: x64 - runner: windows-latest - expected_platform: win-64 - - arch: arm64 - runner: windows-11-arm - expected_platform: win-64 - runs-on: ${{ matrix.runner }} - timeout-minutes: 30 - steps: - - name: Check out repository - uses: actions/checkout@v5 - - - uses: prefix-dev/setup-pixi@v0.10.0 - with: - pixi-version: latest - cache: false - post-cleanup: false - - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: '3.x' - - - name: Generate releases manifest - run: python windows/generate_releases.py - - - name: Verify host Pixi platform detection - shell: pwsh - run: | - $expected = "${{ matrix.expected_platform }}" - $got = (python -c "import sys; sys.path.insert(0,'windows'); import install_ops; print(install_ops.host_pixi_platform())").Trim() - Write-Host "host_pixi_platform() -> $got (expected $expected)" - if ($got -ne $expected) { - Write-Error "Platform detection mismatch: got '$got', expected '$expected'" - exit 1 - } - - - name: Install PyInstaller - run: python -m pip install pyinstaller - - - name: Build Lucy.exe - shell: pwsh - run: | - python -m PyInstaller --noconfirm --onefile --name Lucy ` - --icon windows/assets/lucy-icon.ico ` - --hidden-import install_ops ` - --hidden-import install_runner ` - --paths windows ` - windows/Lucy.py - if (-not (Test-Path "dist/Lucy.exe")) { Write-Error "Lucy.exe not produced"; exit 1 } - - - name: Smoke-test Lucy.exe CLI (bundled imports + prereq report) - shell: pwsh - run: | - dist\Lucy.exe --cli check-prereqs - $code = $LASTEXITCODE - Write-Host "check-prereqs exit code: $code" - if ($code -gt 1) { Write-Error "Lucy.exe --cli check-prereqs crashed (exit $code)"; exit 1 } - exit 0 - - - name: Install NSIS - run: choco install nsis -y --no-progress - - - name: Build Lucy-Setup.exe (NSIS) - shell: pwsh - run: | - $makensis = "${env:ProgramFiles(x86)}\NSIS\makensis.exe" - & $makensis "/DMyAppVersion=0.0.0-ci" windows/installer/Lucy.nsi - if (-not (Test-Path "dist/Lucy-Setup-0.0.0-ci.exe")) { Write-Error "Installer not produced"; exit 1 } - - - name: Upload Windows artifacts - uses: actions/upload-artifact@v5 - with: - name: lucy-windows-${{ matrix.arch }} - path: | - dist/Lucy.exe - dist/Lucy-Setup-0.0.0-ci.exe - if-no-files-found: error - - build-and-release-windows-exe: - name: Build and Release Windows Executable - if: startsWith(github.ref, 'refs/tags/') - runs-on: windows-latest - needs: pixi-install-build-test - permissions: - contents: write - steps: - - name: Check out repository - uses: actions/checkout@v5 - - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: '3.x' - - - name: Generate releases manifest - run: python windows/generate_releases.py - - - name: Install PyInstaller - run: python -m pip install pyinstaller - - - name: Build Lucy.exe - shell: pwsh - run: | - python -m PyInstaller --noconfirm --onefile --name Lucy ` - --icon windows/assets/lucy-icon.ico ` - --hidden-import install_ops ` - --hidden-import install_runner ` - --paths windows ` - windows/Lucy.py - - - name: Install NSIS - run: choco install nsis -y - - - name: Build Lucy-Setup.exe - shell: pwsh - run: | - $version = "${{ github.ref_name }}" -replace '^v','' - if (-not $version) { $version = "0.0.0" } - $makensis = "${env:ProgramFiles(x86)}\NSIS\makensis.exe" - & $makensis "/DMyAppVersion=$version" windows/installer/Lucy.nsi - - - name: Create Release and Upload Assets - uses: softprops/action-gh-release@v2 - with: - files: | - dist/Lucy.exe - dist/Lucy-Setup-*.exe - draft: true diff --git a/.gitignore b/.gitignore index 4151621..58bbbe0 100644 --- a/.gitignore +++ b/.gitignore @@ -39,7 +39,7 @@ config/repos.json.local # Local launcher override (package list/toggles; takes precedence over config/launcher_config.json) config/launcher_config.json.local -# End-user install profile (written by installer / Lucy.exe) +# End-user install profile (written by the Windows installer) config/install.profile.json # Windows executable files @@ -47,4 +47,5 @@ dist/ *.spec *.local -.local/ \ No newline at end of file +.local/ + diff --git a/Lucy.py b/Lucy.py index d8d4ba4..8eba4b7 100644 --- a/Lucy.py +++ b/Lucy.py @@ -1,18 +1,22 @@ #!/usr/bin/env python3 -import curses import os import subprocess import sys import shutil +try: + import curses +except ImportError: # Windows: handled by windows_main + curses = None + MIN_TERM_HEIGHT = 15 MIN_TERM_WIDTH = 65 INSTALL_ENV = {"LUCY_PIXI_AUTO_UPGRADE": "1"} def is_installed(): """True when the workspace has been built (mirrors launch_lucy.sh's check).""" - return os.path.isfile("install/setup.bash") + return any(os.path.isfile(f) for f in ("install/setup.bash", "install/setup.bat")) def get_dev_mode(): if not os.path.exists(".env"): @@ -40,6 +44,43 @@ def set_dev_mode(is_enabled): if not dev_found: f.write(f"DEV={str(is_enabled).lower()}\n") +PIXI_TASKS = ( + ("pixi run core", "robot stack + rosbridge on 9090"), + ("pixi run control-panel", "web UI on http://localhost:4004"), + ("pixi run rviz", "optional viewer"), +) + + +def confirm(prompt): + """Yes unless explicitly declined; assumes yes when nothing can answer.""" + if not sys.stdin.isatty(): + return True + try: + return input(f"{prompt} [Y/n] ").strip().lower() in ("", "y", "yes") + except EOFError: + return True + + +def windows_main(): + """Entry point on Windows, which has neither curses for the TUI nor tmux to drive. + + install.py runs interactively so its pixi and MSVC prompts reach the user. + """ + if not is_installed(): + print("Lucy is not installed in this workspace.") + if not confirm("Install now?"): + return 0 + rc = run_command([sys.executable, "install.py"], interactive=True, extra_env=INSTALL_ENV) + if rc != 0: + print(f"\nInstall failed with exit code {rc}.", file=sys.stderr) + return rc + + print("\nLucy is installed. Start each component in its own terminal:") + for command, purpose in PIXI_TASKS: + print(f" {command:<24} {purpose}") + return 0 + + def prepend_pixi_to_path(): """Prefer ~/.pixi/bin over system/nix pixi (official installer is usually newer).""" pixi_bin = os.path.join(os.path.expanduser("~"), ".pixi", "bin") @@ -222,6 +263,10 @@ def main_tui(stdscr): if __name__ == "__main__": # This initial check is done before curses.wrapper to provide a clean error message # without the screen flicker of initializing and de-initializing curses. + # Windows first: the size check below needs curses, which does not exist there. + if sys.platform == "win32": + sys.exit(windows_main()) + def check_initial_size(): stdscr = curses.initscr() h, w = stdscr.getmaxyx() diff --git a/README.md b/README.md index 5ee3276..a5c6b49 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,7 @@ python install.py Keep the workspace in a path **without spaces**. Pixi console scripts (colcon, pytest, ROS 2 nodes) embed the interpreter path unquoted and cannot start from one; `%LOCALAPPDATA%\Programs\Lucy` is the installer's default. -**End users:** download **`Lucy-Setup.exe`** from [GitHub Releases](https://github.com/Sentience-Robotics/lucy_ws/releases). See the [Windows README](windows/README.md). +Pixi resolves **`win-64`** on both Intel/AMD and Windows-on-ARM hosts — there is no `win-arm64` entry in `pixi.lock`. Reopen your terminal after installing anything above so the updated `PATH` is picked up. ## Quick start diff --git a/docs/developer_lucy_packages.md b/docs/developer_lucy_packages.md index 2bfcdf6..da61f16 100644 --- a/docs/developer_lucy_packages.md +++ b/docs/developer_lucy_packages.md @@ -21,7 +21,7 @@ SSH keys must be configured for GitHub on your host before running `install.py` |------|---------| | [`config/repos.json.local`](../config/repos.json.local) | Forks, feature branches, skip optional repos | | [`config/launcher_config.json.local`](../config/launcher_config.json.local) | Custom Control Center package list (e.g. multi-robot) | -| [`config/install.profile.json`](../config/install.profile.json) | Windows installer choices (written by `Lucy-Setup.exe`) | +| [`config/install.profile.json`](../config/install.profile.json) | Windows installer choices (written by the installer) | Example for a fork — same structure as `repos.json`: @@ -60,19 +60,13 @@ Standard path: `python3 install.py` then `python3 Lucy.py`. ### Windows -End-user install: **`Lucy-Setup.exe`** → **`Lucy.exe`**. Full details: [`windows/README.md`](../windows/README.md). +`python3 install.py` installs, `--repair` re-clones and rebuilds, `--build-only` +skips git. `python3 Lucy.py` runs install when the workspace is missing and +otherwise names the pixi tasks; there is no TUI on Windows (no curses, no tmux). -Developer CLI equivalents: - -| Windows | Linux/macOS | -|---------|-------------| -| `Lucy-Setup.exe` → Fresh install | `python3 install.py` | -| `Lucy-Setup.exe` → Update | `python3 install.py` | -| `Lucy-Setup.exe` → Repair | `python3 install.py --repair` | -| `Lucy.exe` | `./launch_lucy.sh` | -| `Lucy.exe --cli build-only` | `python3 install.py --build-only` | - -Launch runs via Git Bash (`bash launch_lucy.sh`). Without tmux, the Control Center runs directly (`pixi run -- python -m launcher`). +Pixi resolves `win-64` on Intel/AMD and Windows-on-ARM alike — `pixi.lock` has +no `win-arm64`. Colcon uses `--merge-install` there, see +[`docs/pixi_setup.md`](pixi_setup.md). ### Workspace install (`install.py`) @@ -110,7 +104,7 @@ Day-to-day use: one tmux session (Linux/macOS), toggle components in the TUI. |-------|---------| | TUI manager | `python3 Lucy.py` → **Launch** | | Direct | `./launch_lucy.sh` | -| Windows | `Lucy.exe` | +| Windows | `python3 Lucy.py`, then the pixi tasks it lists | | `launch_lucy.sh` flag | Purpose | |-----------------------|---------| @@ -222,7 +216,6 @@ Vite proxies `/rosbridge` to `ws://127.0.0.1:9090`. Launcher sets `LUCY_LCP_*` v | [`docs/launcher_packages.md`](launcher_packages.md) | Adding packages to the Control Center | | [`docs/pixi_setup.md`](pixi_setup.md) | Pixi/RoboStack deps, lock workflow, component tasks | | [`docs/pixi_release.md`](pixi_release.md) | Release packaging (pixi-build-ros) | -| [`windows/README.md`](../windows/README.md) | Windows installer and `Lucy.exe` | | [`src/lucy_ros_packages/docs/DEVELOPER.md`](../src/lucy_ros_packages/docs/DEVELOPER.md) | bringup, ros2_control, CI | | [`src/lucy_ros_packages/doc/ROS2_CONTROL.md`](../src/lucy_ros_packages/doc/ROS2_CONTROL.md) | ros2_control on Lucy | | [`src/inmoov_urdf/docs/DEVELOPER.md`](../src/inmoov_urdf/docs/DEVELOPER.md) | URDF, meshes, sim launches | diff --git a/install.py b/install.py index 7118544..22a35a8 100644 --- a/install.py +++ b/install.py @@ -2,7 +2,7 @@ """Lucy workspace setup: clone sub-repos, install RoboStack deps via Pixi, colcon build. Single cross-platform implementation behind the Windows launcher and the -Windows flows in windows/install_ops.py. Keep behaviour changes here so no +Keep behaviour changes here so no platform drifts from the others. Usage: diff --git a/windows/Lucy.py b/windows/Lucy.py deleted file mode 100644 index f2be2d1..0000000 --- a/windows/Lucy.py +++ /dev/null @@ -1,149 +0,0 @@ -# Windows launcher for the Lucy workspace. -# -# Compiled to Lucy.exe via PyInstaller. Default behaviour: launch via Pixi -# (native RoboStack + Control Center launcher). Install/update/repair is handled -# by Lucy-Setup.exe via the hidden --cli mode (see windows/install_runner.py). -# -# PREREQUISITES: -# 1. Pixi — https://pixi.prefix.dev/latest/installation/ -# 2. Git Bash (runs launch_lucy.sh) — https://git-scm.com/install/windows -# 3. Workspace installed (run Lucy-Setup.exe first) - -import os -import shutil -import subprocess -import sys - -if sys.platform != "win32": - print("Error: This script is designed for Windows only.", file=sys.stderr) - sys.exit(1) - -_WINDOWS_DIR = os.path.dirname(os.path.abspath(__file__)) -if _WINDOWS_DIR not in sys.path: - sys.path.insert(0, _WINDOWS_DIR) - -if getattr(sys, "frozen", False): - PROJECT_ROOT = os.path.dirname(sys.executable) -else: - PROJECT_ROOT = os.path.dirname(_WINDOWS_DIR) - -_CLI_MODES = frozenset(("install", "update", "repair", "build-only", "check-prereqs")) - - -def run_command(command, check=True, interactive=False): - """Runs a command, streaming its output if not interactive.""" - print(f"--- Running: {' '.join(command)} ---") - try: - if interactive: - return subprocess.run(command, check=check, cwd=PROJECT_ROOT).returncode - process = subprocess.Popen( - command, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - text=True, - cwd=PROJECT_ROOT, - ) - for line in iter(process.stdout.readline, ""): - print(line.rstrip()) - process.wait() - if check and process.returncode != 0: - raise subprocess.CalledProcessError(process.returncode, command) - return process.returncode - except FileNotFoundError: - print(f"Error: Command '{command[0]}' not found. Is it in your PATH?") - return -1 - except subprocess.CalledProcessError as e: - print(f"Command failed with exit code {e.returncode}") - return e.returncode - - -def _workspace_built(): - install_dir = os.path.join(PROJECT_ROOT, "install") - return ( - os.path.isfile(os.path.join(install_dir, "setup.bat")) - or os.path.isfile(os.path.join(install_dir, "setup.bash")) - ) - - -def _find_git_bash(): - """Find Git for Windows bash.exe, never the WSL bash.exe.""" - candidates = [ - os.path.join(os.environ.get("ProgramFiles", r"C:\Program Files"), - "Git", "bin", "bash.exe"), - os.path.join(os.environ.get("ProgramW6432", r"C:\Program Files"), - "Git", "bin", "bash.exe"), - os.path.join(os.environ.get("ProgramFiles(x86)", r"C:\Program Files (x86)"), - "Git", "bin", "bash.exe"), - ] - - for candidate in candidates: - if os.path.isfile(candidate): - return candidate - - # Git Bash may also be discoverable through git.exe. - git = shutil.which("git") - if git: - git_root = os.path.dirname(os.path.dirname(os.path.abspath(git))) - candidate = os.path.join(git_root, "bin", "bash.exe") - if os.path.isfile(candidate): - return candidate - - return None - - -def launch_workspace(): - """Start Pixi and attach to the Lucy Control Center launcher.""" - if not _workspace_built(): - print( - "Workspace not built. Run Lucy-Setup.exe to install or update first.", - file=sys.stderr, - ) - sys.exit(1) - - if shutil.which("pixi") is None: - print( - "Missing pixi. Install: https://pixi.prefix.dev/latest/installation/", - file=sys.stderr, - ) - sys.exit(1) - - bash = _find_git_bash() - launch_script = os.path.join(PROJECT_ROOT, "launch_lucy.sh") - if not bash: - print( - "Git Bash (bash) is required to run launch_lucy.sh on Windows.", - file=sys.stderr, - ) - print("Install Git for Windows: https://git-scm.com/install/windows", file=sys.stderr) - sys.exit(1) - if not os.path.isfile(launch_script): - print(f"Missing launch script: {launch_script}", file=sys.stderr) - sys.exit(1) - - print("Launching workspace...") - run_command([bash, launch_script], interactive=True) - - -def _is_cli_invocation(): - return len(sys.argv) > 1 and (sys.argv[1] == "--cli" or sys.argv[1] in _CLI_MODES) - - -def _run_cli(): - """Install/update/repair — used by Lucy-Setup.exe, not exposed in the default UX.""" - from install_runner import main as install_main - - argv = [a for a in sys.argv[1:] if a != "--cli"] - return install_main(argv) - - -if __name__ == "__main__": - os.chdir(PROJECT_ROOT) - try: - if _is_cli_invocation(): - sys.exit(_run_cli()) - launch_workspace() - except KeyboardInterrupt: - print("\nExiting.") - except Exception as e: - print(f"An unexpected error occurred: {e}", file=sys.stderr) - sys.exit(1) diff --git a/windows/README.md b/windows/README.md deleted file mode 100644 index d24d68e..0000000 --- a/windows/README.md +++ /dev/null @@ -1,108 +0,0 @@ -# Windows launcher and installer - -On Windows, Lucy is split into two programs: - -| Program | Purpose | -|---------|---------| -| **`Lucy-Setup.exe`** | Install, update, repair, pick version, developer mode | -| **`Lucy.exe`** | Launch the workspace (Pixi → Control Center) | - -`windows/Lucy.py` is the PyInstaller source for `Lucy.exe`. It launches the workspace directly — there is no install menu. Use **`Lucy-Setup.exe`** for all install lifecycle tasks. - -## Prerequisites - -Install the following before running the project. After each installation, close and reopen any terminal so the updated `PATH` is picked up. - -1. **Pixi** — [pixi.prefix.dev/latest/installation](https://pixi.prefix.dev/latest/installation/) (≥ 0.78 recommended). -2. **Git for Windows** — [git-scm.com/install/windows](https://git-scm.com/install/windows). - - Required for `bash launch_lucy.sh` (default launch path). - - Without Git, the installer downloads sub-repositories as ZIP archives. -3. **Python 3** (manual dev workflow only) — [python.org/downloads](https://www.python.org/downloads/). - -GUI apps (RViz, Gazebo, rqt) run **natively** on Windows via RoboStack when OpenGL/display support is available. The control panel web viewer does not require a separate X server. - -### CPU architecture (x64 / ARM64) - -Pixi resolves **`win-64`** from `pixi.lock` on Intel/AMD and Windows-on-ARM hosts. Colcon uses `--merge-install` on Windows per RoboStack guidance. - -## Installation (end users) - -Download **`Lucy-Setup.exe`** from the [GitHub Releases](https://github.com/Sentience-Robotics/lucy_ws/releases) page (built automatically on version tags). - -The installer: - -- Installs Lucy to `%LOCALAPPDATA%\Programs\Lucy` (no admin required) -- Creates a **Start Menu** shortcut to `Lucy.exe` -- Lets you choose **Fresh install**, **Update**, or **Repair** -- Lets you pick a **lucy_ws version** (latest `master` or a release tag) -- Runs install/update after setup (clones sub-repos, `pixi install`, colcon build) -- Offers **Developer install** (off by default): requires Git, uses SSH clones and `DEV=true` - -After setup, open **Lucy** from the Start Menu — it runs `bash launch_lucy.sh` and opens the Control Center. - -To **update** or **repair**, run **`Lucy-Setup.exe`** again and pick the matching install mode. - -### Control Panel - -In the **Lucy Control Center**, enable **Core + Control Panel**. The panel is at [http://localhost:4004](http://localhost:4004). The launcher prints the exact URL when the panel is running. - -## Manual install (developers) - -Clone the repo, then install via CLI (same logic as the installer): - -```powershell -cd C:\Users\\lucy_ws -python windows\Lucy.py --cli install --repos-branch master -``` - -Or use Pixi directly from Git Bash / WSL: - -```bash -python3 install.py -pixi run build -pixi run panel-install -``` - -Launch: - -```powershell -python windows\Lucy.py -``` - -Or from Git Bash: `./launch_lucy.sh` or `python3 Lucy.py` (full TUI — see the main [README](../README.md)). - -### Advanced CLI (installer internals) - -`Lucy.exe --cli` is used by `Lucy-Setup.exe` and available for scripting: - -```powershell -Lucy.exe --cli check-prereqs -Lucy.exe --cli install --repos-branch master -Lucy.exe --cli update -Lucy.exe --cli repair -Lucy.exe --cli install --developer --refresh-workspace --lucy-ws-ref v1.0.0 --lucy-ws-ref-type tag -``` - -### Building the installer locally - -Requires [NSIS](https://nsis.sourceforge.io/Download) and PyInstaller: - -```powershell -powershell -ExecutionPolicy Bypass -File windows/build_installer.ps1 -``` - -Outputs `dist\Lucy.exe` and `dist\Lucy-Setup-.exe`. - -### Application icon - -The icon is [`windows/assets/lucy-icon.ico`](assets/lucy-icon.ico). To regenerate from a square logo JPG: - -```powershell -pip install pillow -python -c "from PIL import Image; Image.open('path\to\lucy-logo.jpg').save('windows/assets/lucy-icon.ico', sizes=[(256,256),(128,128),(64,64),(48,48),(32,32),(16,16)])" -``` - -## Terminal choice - -- **Native Windows:** `Lucy.exe` (installed) or `python windows/Lucy.py` (from a clone) — uses Git Bash for launch. -- **Git Bash / WSL:** root `Lucy.py`, `install.py`, and `launch_lucy.sh` (recommended for developers). diff --git a/windows/assets/lucy-icon.ico b/windows/assets/lucy-icon.ico deleted file mode 100644 index 5dce64a..0000000 Binary files a/windows/assets/lucy-icon.ico and /dev/null differ diff --git a/windows/build_installer.ps1 b/windows/build_installer.ps1 deleted file mode 100644 index adce087..0000000 --- a/windows/build_installer.ps1 +++ /dev/null @@ -1,61 +0,0 @@ -# Build Lucy.exe and Lucy-Setup.exe on Windows. -# Requires: Python 3, pip, PyInstaller, NSIS (https://nsis.sourceforge.io/Download) -# -# Usage (from repo root): -# powershell -ExecutionPolicy Bypass -File windows/build_installer.ps1 -# powershell -ExecutionPolicy Bypass -File windows/build_installer.ps1 -Version 1.0.0 - -param( - [string]$Version = "" -) - -$ErrorActionPreference = "Stop" -$Root = Split-Path -Parent (Split-Path -Parent $MyInvocation.MyCommand.Path) -Set-Location $Root - -Write-Host "=== Generating releases manifest ===" -python windows/generate_releases.py - -Write-Host "=== Building Lucy.exe (PyInstaller) ===" -python -m pip install --quiet pyinstaller -$icon = Join-Path $Root "windows\assets\lucy-icon.ico" -if (-not (Test-Path $icon)) { - Write-Error "Missing icon: $icon" -} -python -m PyInstaller --noconfirm --onefile --name Lucy ` - --icon $icon ` - --hidden-import install_ops ` - --hidden-import install_runner ` - --paths (Join-Path $Root "windows") ` - (Join-Path $Root "windows\Lucy.py") - -if (-not (Test-Path "dist\Lucy.exe")) { - Write-Error "PyInstaller did not produce dist\Lucy.exe" -} - -$MakeNsis = @( - (Get-Command makensis -ErrorAction SilentlyContinue).Source, - "${env:ProgramFiles(x86)}\NSIS\makensis.exe", - "$env:ProgramFiles\NSIS\makensis.exe" -) | Where-Object { $_ -and (Test-Path $_) } | Select-Object -First 1 - -if (-not $MakeNsis) { - Write-Warning "NSIS (makensis) not found. Lucy.exe is at dist\Lucy.exe" - Write-Warning "Install NSIS to build Lucy-Setup.exe: https://nsis.sourceforge.io/Download" - exit 0 -} - -if (-not $Version) { - try { - $tag = git describe --tags --exact-match 2>$null - if ($tag -match '^v(.+)$') { $Version = $Matches[1] } - } catch {} -} -if (-not $Version) { $Version = "0.0.0-dev" } - -Write-Host "=== Building Lucy-Setup.exe (NSIS, version $Version) ===" -& $MakeNsis "/DMyAppVersion=$Version" (Join-Path $Root "windows\installer\Lucy.nsi") - -Write-Host "=== Done ===" -Write-Host " dist\Lucy.exe" -Write-Host " dist\Lucy-Setup-$Version.exe" diff --git a/windows/generate_releases.py b/windows/generate_releases.py deleted file mode 100644 index 944aa65..0000000 --- a/windows/generate_releases.py +++ /dev/null @@ -1,78 +0,0 @@ -#!/usr/bin/env python3 -"""Generate windows/releases.json from lucy_ws git tags (run at installer build time).""" - -from __future__ import annotations - -import json -import subprocess -import sys -from pathlib import Path - - -def _escape_nsis(text: str) -> str: - """Escape a string for an NSIS double-quoted argument.""" - return text.replace('$', '$$').replace('"', '$\\"') - - -def write_nsh(out_nsh: Path, releases: list[dict]) -> None: - """Emit an NSIS include providing macro LUCY_ADD_RELEASES .""" - lines = [ - "; Auto-generated by windows/generate_releases.py — do not edit.", - "!define LUCY_RELEASES_INCLUDED", - "!macro LUCY_ADD_RELEASES HWND", - ] - for rel in releases: - lines.append(f' ${{NSD_CB_AddString}} ${{HWND}} "{_escape_nsis(rel["label"])}"') - lines.append("!macroend") - out_nsh.write_text("\n".join(lines) + "\n", encoding="utf-8") - - -def main() -> int: - root = Path(__file__).resolve().parents[1] - out = root / "windows" / "releases.json" - out_nsh = root / "windows" / "releases.nsh" - - try: - result = subprocess.run( - ["git", "tag", "-l", "v*"], - cwd=root, - capture_output=True, - text=True, - check=True, - ) - tags = sorted( - [t.strip() for t in result.stdout.splitlines() if t.strip()], - reverse=True, - ) - except (subprocess.CalledProcessError, FileNotFoundError): - tags = [] - - releases = [ - { - "id": "latest", - "label": "Latest (master)", - "ref": "master", - "ref_type": "branch", - "recommended": True, - } - ] - for tag in tags: - releases.append({ - "id": tag, - "label": tag, - "ref": tag, - "ref_type": "tag", - "recommended": False, - }) - - payload = {"generated_by": "windows/generate_releases.py", "releases": releases} - out.write_text(json.dumps(payload, indent=2) + "\n", encoding="utf-8") - print(f"Wrote {out} ({len(releases)} entries)") - - write_nsh(out_nsh, releases) - print(f"Wrote {out_nsh} ({len(releases)} entries)") - return 0 - - -if __name__ == "__main__": - sys.exit(main()) diff --git a/windows/install_ops.py b/windows/install_ops.py deleted file mode 100644 index ac45f9a..0000000 --- a/windows/install_ops.py +++ /dev/null @@ -1,259 +0,0 @@ -""" -Windows-specific install glue for Lucy. - -Used by windows/Lucy.py (launcher + CLI) and the NSIS installer via install_runner.py. - -The actual install logic (repo fetching, Pixi bootstrap, colcon build) lives in the -cross-platform install.py at the repo root; this module adds only what is specific -to the packaged Windows flows: the install profile, host platform detection, and -refreshing the lucy_ws files themselves. Re-exports below keep the historical -install_ops.* API working for callers and the frozen Lucy.exe. -""" - -from __future__ import annotations - -import os -import platform -import shutil -import sys -import tempfile -import urllib.request -import zipfile -import json -from datetime import datetime, timezone -from typing import Callable, Optional - -_WINDOWS_DIR = os.path.dirname(os.path.abspath(__file__)) -_PROJECT_ROOT = os.path.dirname(_WINDOWS_DIR) -if _PROJECT_ROOT not in sys.path: - sys.path.insert(0, _PROJECT_ROOT) - -import install as _install # noqa: E402 - -# --- re-exported cross-platform API (implemented in install.py) -------------- -PrerequisiteError = _install.PrerequisiteError -REQUIREMENT_DOCS = _install.REQUIREMENT_DOCS -MIN_PIXI_VERSION = _install.MIN_PIXI_VERSION -DEFAULT_REPOS_BRANCH = _install.DEFAULT_REPOS_BRANCH -InstallMode = _install.InstallMode - -git_available = _install.git_available -pixi_available = _install.pixi_available -python_available = _install.python_available -git_identity_warnings = _install.git_identity_warnings -check_prerequisites = _install.check_prerequisites -print_prerequisite_report = _install.print_prerequisite_report -require_prerequisites = _install.require_prerequisites -ensure_pixi = _install.ensure_pixi - -parse_repos = _install.parse_repos -github_zip_url = _install.github_zip_url -fetch_repo = _install.fetch_repo -fetch_repo_git = _install.fetch_repo_git -fetch_repo_zip = _install.fetch_repo_zip -install_repos = _install.install_repos -mark_optional_colcon_ignore = _install.mark_optional_colcon_ignore -remove_workspace_src_repo = _install.remove_workspace_src_repo -remove_build_artifacts = _install.remove_build_artifacts -pixi_install = _install.pixi_install -build_workspace = _install.build_workspace - -_safe_rmtree = _install.safe_rmtree - -LUCY_WS_GITHUB = "Sentience-Robotics/lucy_ws" - - -# --- install profile --------------------------------------------------------- - - -def _repos_config_path(project_root: str) -> str: - return str(_install.repos_config_path(project_root)) - - -def install_profile_path(project_root: str) -> str: - return os.path.join(project_root, "config", "install.profile.json") - - -def load_install_profile(project_root: str) -> dict: - path = install_profile_path(project_root) - if not os.path.exists(path): - return {} - with open(path, "r", encoding="utf-8") as f: - return json.load(f) - - -def save_install_profile(project_root: str, profile: dict) -> None: - path = install_profile_path(project_root) - os.makedirs(os.path.dirname(path), exist_ok=True) - with open(path, "w", encoding="utf-8") as f: - json.dump(profile, f, indent=2) - f.write("\n") - - -def default_profile(developer: bool = False, fetch_method: str = "git") -> dict: - return { - "lucy_ws_ref": "master", - "lucy_ws_ref_type": "branch", - "repos_branch": DEFAULT_REPOS_BRANCH, - "fetch_method": fetch_method, - "developer": developer, - "installed_at": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"), - } - - -def merge_profile(project_root: str, **overrides) -> dict: - profile = default_profile() - profile.update(load_install_profile(project_root)) - profile.update({k: v for k, v in overrides.items() if v is not None}) - return profile - - -# --- host platform ----------------------------------------------------------- - - -def _native_machine() -> str: - """Best-effort *native* CPU arch, seeing through Windows x64 emulation. - - A 64-bit x86 build of Lucy.exe runs emulated on Windows ARM, where - platform.machine() reports AMD64. PROCESSOR_ARCHITEW6432 holds the true - native arch in that WOW64/emulation case; fall back to the normal vars. - """ - if sys.platform == "win32": - for var in ("PROCESSOR_ARCHITEW6432", "PROCESSOR_ARCHITECTURE"): - value = os.environ.get(var, "").strip().lower() - if value: - return value - return platform.machine().lower() - - -def host_pixi_platform() -> str: - """Map the native host to a Pixi platform id (pixi.toml / pixi.lock).""" - machine = _native_machine() - if sys.platform == "win32": - return "win-64" - if sys.platform == "darwin": - if machine in ("aarch64", "arm64"): - return "osx-arm64" - return "osx-64" - if machine in ("aarch64", "arm64"): - return "linux-aarch64" - return "linux-64" - - -def host_container_platform() -> str: - """Legacy alias used by Windows CI — returns Pixi platform, not Docker.""" - return host_pixi_platform() - - -# --- dev mode ---------------------------------------------------------------- - - -def set_dev_mode(project_root: str, enabled: bool) -> None: - env_path = os.path.join(project_root, ".env") - lines: list[str] = [] - dev_found = False - if os.path.exists(env_path): - with open(env_path, "r", encoding="utf-8") as f: - lines = f.readlines() - with open(env_path, "w", encoding="utf-8") as f: - for line in lines: - if line.strip().startswith("DEV="): - f.write(f"DEV={str(enabled).lower()}\n") - dev_found = True - else: - f.write(line) - if not dev_found: - f.write(f"DEV={str(enabled).lower()}\n") - - -# --- flows ------------------------------------------------------------------- - - -def run_install_flow( - project_root: str, - mode: InstallMode, - *, - developer: Optional[bool] = None, - repos_branch: Optional[str] = None, - fetch_method: Optional[str] = None, - run_command: Callable, - log: Callable[[str], None] = print, -) -> dict: - """Full install/update/repair/build-only flow. Returns updated install profile.""" - profile = merge_profile(project_root) - if developer is not None: - profile["developer"] = developer - if repos_branch is not None: - profile["repos_branch"] = repos_branch - if fetch_method is not None: - profile["fetch_method"] = fetch_method - - dev = bool(profile.get("developer", False)) - # DEV lands in .env before the flow reads it, so SSH clones match the profile. - set_dev_mode(project_root, dev) - - result = _install.run_flow( - project_root, - mode, - developer=dev, - repos_branch=profile.get("repos_branch", DEFAULT_REPOS_BRANCH), - fetch_method=profile.get("fetch_method") or "auto", - run_command=run_command, - log=log, - ) - - profile["fetch_method"] = result["fetch_method"] - profile["installed_at"] = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ") - save_install_profile(project_root, profile) - return profile - - -def fetch_lucy_ws_snapshot( - project_root: str, - ref: str, - ref_type: str, - *, - fetch_method: str, - run_command: Callable, - log: Callable[[str], None] = print, -) -> None: - """Refresh lucy_ws workspace files from GitHub at ref (branch or tag).""" - use_git = fetch_method == "git" and git_available() - - if use_git and os.path.isdir(os.path.join(project_root, ".git")): - log(f"Updating lucy_ws to {ref} ...") - run_command(["git", "-C", project_root, "fetch", "origin"]) - run_command(["git", "-C", project_root, "checkout", ref]) - run_command(["git", "-C", project_root, "pull", "--ff-only", "origin", ref], check=False) - return - - zip_url = ( - f"https://github.com/{LUCY_WS_GITHUB}/archive/refs/tags/{ref}.zip" - if ref_type == "tag" - else f"https://github.com/{LUCY_WS_GITHUB}/archive/refs/heads/{ref}.zip" - ) - log(f"Downloading lucy_ws snapshot from {zip_url}") - with tempfile.TemporaryDirectory() as tmp: - zip_path = os.path.join(tmp, "lucy_ws.zip") - urllib.request.urlretrieve(zip_url, zip_path) - extract_root = os.path.join(tmp, "extract") - os.makedirs(extract_root, exist_ok=True) - with zipfile.ZipFile(zip_path, "r") as zf: - top_levels = {n.split("/")[0] for n in zf.namelist() if n.strip()} - zf.extractall(extract_root) - if len(top_levels) != 1: - raise RuntimeError(f"Unexpected lucy_ws archive layout: {top_levels}") - source = os.path.join(extract_root, next(iter(top_levels))) - for name in os.listdir(source): - if name in (".git", "src", "build", "install", "log"): - continue - src = os.path.join(source, name) - dst = os.path.join(project_root, name) - if os.path.isdir(dst): - _safe_rmtree(dst) - elif os.path.exists(dst): - os.remove(dst) - if os.path.isdir(src): - shutil.copytree(src, dst) - else: - shutil.copy2(src, dst) diff --git a/windows/install_runner.py b/windows/install_runner.py deleted file mode 100644 index 36833af..0000000 --- a/windows/install_runner.py +++ /dev/null @@ -1,146 +0,0 @@ -#!/usr/bin/env python3 -"""CLI entry point for Lucy Windows install flows (used by Lucy.exe and the NSIS installer).""" - -from __future__ import annotations - -import argparse -import os -import sys - -# Allow running as script from repo: python windows/install_runner.py -_WINDOWS_DIR = os.path.dirname(os.path.abspath(__file__)) -if _WINDOWS_DIR not in sys.path: - sys.path.insert(0, _WINDOWS_DIR) - -import install_ops # noqa: E402 - - -def _project_root() -> str: - if getattr(sys, "frozen", False): - return os.path.dirname(sys.executable) - return os.path.dirname(_WINDOWS_DIR) - - -def _make_run_command(): - def run_command(command, check=True, interactive=False): - import subprocess - print(f"--- Running: {' '.join(command)} ---") - try: - if interactive: - return subprocess.run(command, check=check).returncode - process = subprocess.Popen( - command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True - ) - for line in iter(process.stdout.readline, ""): - print(line.rstrip()) - process.wait() - if check and process.returncode != 0: - raise subprocess.CalledProcessError(process.returncode, command) - return process.returncode - except FileNotFoundError: - print(f"Error: Command '{command[0]}' not found. Is it in your PATH?") - if check: - raise - return -1 - return run_command - - -def main(argv: list[str] | None = None) -> int: - parser = argparse.ArgumentParser(description="Lucy Windows install helper") - parser.add_argument( - "mode", - choices=["install", "update", "repair", "build-only", "check-prereqs"], - help="Install operation to run", - ) - parser.add_argument("--developer", action="store_true", help="Developer install (requires git, SSH clones)") - parser.add_argument("--repos-branch", default=None, help="Fallback branch for repos without one set") - parser.add_argument("--lucy-ws-ref", default="master", help="lucy_ws git ref (branch or tag)") - parser.add_argument( - "--lucy-ws-ref-type", - choices=["branch", "tag"], - default="branch", - help="Whether --lucy-ws-ref is a branch or tag", - ) - parser.add_argument( - "--fetch-method", - choices=["git", "zip", "auto"], - default="auto", - help="How to fetch repositories", - ) - parser.add_argument( - "--refresh-workspace", - action="store_true", - help="Re-download lucy_ws files at --lucy-ws-ref before install", - ) - parser.add_argument( - "--launch-after", - action="store_true", - help="Launch the workspace after a successful install", - ) - args = parser.parse_args(argv) - - root = _project_root() - os.chdir(root) - run_command = _make_run_command() - - if args.mode == "check-prereqs": - issues, warnings = install_ops.check_prerequisites(developer=args.developer) - install_ops.print_prerequisite_report(issues, warnings) - return 1 if issues else 0 - - fetch_method = args.fetch_method - if fetch_method == "auto": - fetch_method = "git" if install_ops.git_available() else "zip" - - profile = install_ops.merge_profile( - root, - lucy_ws_ref=args.lucy_ws_ref, - lucy_ws_ref_type=args.lucy_ws_ref_type, - repos_branch=args.repos_branch, - fetch_method=fetch_method, - developer=args.developer, - ) - install_ops.save_install_profile(root, profile) - - if args.refresh_workspace: - install_ops.fetch_lucy_ws_snapshot( - root, - args.lucy_ws_ref, - args.lucy_ws_ref_type, - fetch_method=fetch_method, - run_command=run_command, - ) - - try: - install_ops.run_install_flow( - root, - args.mode, - developer=args.developer, - repos_branch=args.repos_branch, - fetch_method=fetch_method, - run_command=run_command, - ) - except install_ops.PrerequisiteError: - return 1 - except Exception as exc: - print(f"Install failed: {exc}", file=sys.stderr) - return 1 - - print(f"--- Task '{args.mode}' finished successfully. ---") - - if args.launch_after and args.mode != "check-prereqs": - print("\n--- Launching Lucy... ---") - return _launch_workspace() - - return 0 - - -def _launch_workspace() -> int: - """Hand off to the workspace launcher in the current console window.""" - import Lucy # noqa: WPS433 (Lucy.py exposes launch_workspace) - Lucy.launch_workspace() - return 0 - - -if __name__ == "__main__": - sys.exit(main()) diff --git a/windows/installer/Lucy.nsi b/windows/installer/Lucy.nsi deleted file mode 100644 index 47b3700..0000000 --- a/windows/installer/Lucy.nsi +++ /dev/null @@ -1,342 +0,0 @@ -; Lucy Windows installer (NSIS) — bundles the workspace + Lucy.exe, then runs the -; install via "Lucy.exe --cli ...". NSIS is used (instead of Inno Setup) because -; nsExec::ExecToLog streams the long pixi/colcon build output live into the -; installer's details log. -; -; Build: windows/build_installer.ps1 (requires PyInstaller + NSIS / makensis) - -Unicode true - -!ifndef MyAppVersion - !define MyAppVersion "0.0.0-dev" -!endif - -!define MyAppName "Lucy" -!define MyAppPublisher "Sentience Robotics" -!define MyAppURL "https://github.com/Sentience-Robotics/lucy_ws" -!define MyAppExeName "Lucy.exe" - -!define DOC_PIXI "https://pixi.prefix.dev/latest/installation/" -!define DOC_GIT "https://git-scm.com/install/windows" -!define DOC_PYTHON "https://www.python.org/downloads/" - -!include "MUI2.nsh" -!include "nsDialogs.nsh" -!include "LogicLib.nsh" -!include "x64.nsh" - -Name "${MyAppName}" -OutFile "..\..\dist\Lucy-Setup-${MyAppVersion}.exe" -InstallDir "$LOCALAPPDATA\Programs\Lucy" -RequestExecutionLevel user -SetCompressor /SOLID lzma -ShowInstDetails show - -VIProductVersion "0.0.0.0" -VIAddVersionKey "ProductName" "${MyAppName}" -VIAddVersionKey "ProductVersion" "${MyAppVersion}" -VIAddVersionKey "CompanyName" "${MyAppPublisher}" -VIAddVersionKey "FileDescription" "${MyAppName} Setup" -VIAddVersionKey "FileVersion" "${MyAppVersion}" -VIAddVersionKey "LegalCopyright" "${MyAppPublisher}" - -; ---------------------------------------------------------------------------- -; State collected from the wizard -; ---------------------------------------------------------------------------- -Var InstallMode ; install | update | repair -Var DeveloperInstall ; 1 | 0 -Var RefreshWorkspace ; 1 | 0 -Var SelectedRef ; e.g. master or v1.2.3 -Var SelectedRefType ; branch | tag -Var InstallOk ; 1 only when prereqs + install succeeded - -; nsDialogs control handles -Var ModeCombo -Var VersionCombo -Var DevCheck -Var RefreshCheck -Var PixiCheck -Var PrereqText - -; ---------------------------------------------------------------------------- -; Release list (version dropdown) — generated by windows/generate_releases.py. -; Provides macro LUCY_ADD_RELEASES . Fallback below if absent. -; ---------------------------------------------------------------------------- -!include /NONFATAL "..\releases.nsh" -!ifndef LUCY_RELEASES_INCLUDED - !macro LUCY_ADD_RELEASES HWND - ${NSD_CB_AddString} ${HWND} "Latest (master)" - !macroend -!endif - -; ---------------------------------------------------------------------------- -; MUI configuration -; ---------------------------------------------------------------------------- -!define MUI_ABORTWARNING -!define MUI_ICON "..\assets\lucy-icon.ico" -!define MUI_UNICON "..\assets\lucy-icon.ico" - -!define MUI_FINISHPAGE_RUN -!define MUI_FINISHPAGE_RUN_TEXT "Launch Lucy now" -!define MUI_FINISHPAGE_RUN_FUNCTION "LaunchLucy" -!define MUI_FINISHPAGE_LINK "Lucy on GitHub" -!define MUI_FINISHPAGE_LINK_LOCATION "${MyAppURL}" - -!insertmacro MUI_PAGE_WELCOME -Page custom ModePageCreate ModePageLeave -!insertmacro MUI_PAGE_DIRECTORY -Page custom OptionsPageCreate OptionsPageLeave -Page custom PrereqPageCreate PrereqPageLeave -!insertmacro MUI_PAGE_INSTFILES -!insertmacro MUI_PAGE_FINISH - -!insertmacro MUI_UNPAGE_CONFIRM -!insertmacro MUI_UNPAGE_INSTFILES - -!insertmacro MUI_LANGUAGE "English" - -; ---------------------------------------------------------------------------- -; Defaults -; ---------------------------------------------------------------------------- -Function .onInit - StrCpy $InstallMode "install" - StrCpy $DeveloperInstall "0" - StrCpy $RefreshWorkspace "1" - StrCpy $SelectedRef "master" - StrCpy $SelectedRefType "branch" - StrCpy $InstallOk "0" -FunctionEnd - -; ---------------------------------------------------------------------------- -; Page 1 — install mode -; ---------------------------------------------------------------------------- -Function ModePageCreate - !insertmacro MUI_HEADER_TEXT "Install mode" "Choose how to set up Lucy on this machine." - nsDialogs::Create 1018 - Pop $0 - ${If} $0 == error - Abort - ${EndIf} - - ${NSD_CreateLabel} 0 0 100% 12u "Install mode:" - Pop $1 - - ${NSD_CreateDropList} 0 16u 100% 80u "" - Pop $ModeCombo - ${NSD_CB_AddString} $ModeCombo "Fresh install" - ${NSD_CB_AddString} $ModeCombo "Update existing" - ${NSD_CB_AddString} $ModeCombo "Repair" - ${NSD_CB_SelectString} $ModeCombo "Fresh install" - - nsDialogs::Show -FunctionEnd - -Function ModePageLeave - ${NSD_GetText} $ModeCombo $0 - ${If} $0 == "Update existing" - StrCpy $InstallMode "update" - ${ElseIf} $0 == "Repair" - StrCpy $InstallMode "repair" - ${Else} - StrCpy $InstallMode "install" - ${EndIf} -FunctionEnd - -; ---------------------------------------------------------------------------- -; Page 2 — options (version + developer + refresh) -; ---------------------------------------------------------------------------- -Function OptionsPageCreate - !insertmacro MUI_HEADER_TEXT "Options" "Version and developer settings." - nsDialogs::Create 1018 - Pop $0 - ${If} $0 == error - Abort - ${EndIf} - - ${NSD_CreateLabel} 0 0 100% 12u "lucy_ws version:" - Pop $1 - - ${NSD_CreateDropList} 0 16u 100% 80u "" - Pop $VersionCombo - !insertmacro LUCY_ADD_RELEASES $VersionCombo - SendMessage $VersionCombo ${CB_SETCURSEL} 0 0 - - ${NSD_CreateCheckbox} 0 44u 100% 12u "Developer install (requires Git; uses SSH clones and DEV mode)" - Pop $DevCheck - - ${NSD_CreateCheckbox} 0 60u 100% 24u "Download selected lucy_ws version from GitHub (recommended when not using Latest)" - Pop $RefreshCheck - ${If} $RefreshWorkspace == "1" - ${NSD_Check} $RefreshCheck - ${EndIf} - - nsDialogs::Show -FunctionEnd - -Function OptionsPageLeave - ${NSD_GetState} $DevCheck $0 - ${If} $0 == ${BST_CHECKED} - StrCpy $DeveloperInstall "1" - ${Else} - StrCpy $DeveloperInstall "0" - ${EndIf} - - ${NSD_GetState} $RefreshCheck $0 - ${If} $0 == ${BST_CHECKED} - StrCpy $RefreshWorkspace "1" - ${Else} - StrCpy $RefreshWorkspace "0" - ${EndIf} - - ; Derive ref/type from the label. Tag entries use label == ref (ref_type=tag); - ; the "Latest (master)" entry maps to the master branch. - ${NSD_GetText} $VersionCombo $0 - StrCpy $1 $0 6 - ${If} $1 == "Latest" - StrCpy $SelectedRef "master" - StrCpy $SelectedRefType "branch" - ${Else} - StrCpy $SelectedRef $0 - StrCpy $SelectedRefType "tag" - ${EndIf} -FunctionEnd - -; ---------------------------------------------------------------------------- -; Page 3 — requirements (scrollable text + confirmation checkbox) -; ---------------------------------------------------------------------------- -Function PrereqPageCreate - !insertmacro MUI_HEADER_TEXT "Requirements" "Scroll for the full list. URLs are selectable; copy one into your browser." - - nsDialogs::Create 1018 - Pop $0 - ${If} $0 == error - Abort - ${EndIf} - - StrCpy $PrereqText "Required:$\r$\n Pixi$\r$\n ${DOC_PIXI}$\r$\n Git for Windows (launch via bash launch_lucy.sh)$\r$\n ${DOC_GIT}$\r$\n$\r$\nOptional (developers):$\r$\n Python 3$\r$\n ${DOC_PYTHON}$\r$\n Without Git, repositories are downloaded as ZIP archives." - - ; Read-only multiline edit; reliably displays/wraps and scrolls if needed. - ${NSD_CreateMLText} 0 0 100% -22u $PrereqText - Pop $1 - ${NSD_Edit_SetReadOnly} $1 1 - - ${NSD_CreateCheckbox} 0 -18u 100% 12u "Pixi is installed" - Pop $PixiCheck - - nsDialogs::Show -FunctionEnd - -Function PrereqPageLeave - ${NSD_GetState} $PixiCheck $0 - ${If} $0 != ${BST_CHECKED} - MessageBox MB_OK|MB_ICONEXCLAMATION "Please confirm that Pixi is installed before continuing." - Abort - ${EndIf} -FunctionEnd - -; ---------------------------------------------------------------------------- -; Prerequisite failure dialog (mirrors the old Inno ShowPrerequisitesFailed) -; ---------------------------------------------------------------------------- -Function ShowPrerequisitesFailed - StrCpy $0 "Required software is missing or not available, so the workspace install was not started.$\r$\n$\r$\nRequired:$\r$\n Pixi must be installed and on PATH.$\r$\n" - ${If} $DeveloperInstall == "1" - StrCpy $0 "$0$\r$\nDeveloper install also requires Git for Windows.$\r$\n" - ${EndIf} - StrCpy $0 "$0$\r$\nLucy was copied to your PC. After fixing the items above, run Lucy-Setup.exe again and choose Update.$\r$\n$\r$\nOpen the Pixi install page now?" - MessageBox MB_YESNO|MB_ICONEXCLAMATION "$0" IDNO +2 - ExecShell "open" "${DOC_PIXI}" -FunctionEnd - -; ---------------------------------------------------------------------------- -; Install -; ---------------------------------------------------------------------------- -Section "Install" - SetOutPath "$INSTDIR" - File "..\..\dist\${MyAppExeName}" - File "..\..\pixi.toml" - File "..\..\pixi.lock" - File "..\..\install.py" - File "..\..\launch_lucy.sh" - File "..\..\Lucy.py" - File "..\..\README.md" - File /nonfatal "..\..\*.py" - File /nonfatal "..\..\*.sh" - - SetOutPath "$INSTDIR\config" - File /r "..\..\config\*" - - SetOutPath "$INSTDIR\windows" - File /r /x "installer" /x "build_installer.ps1" "..\..\windows\*" - - SetOutPath "$INSTDIR\windows\assets" - File "..\assets\lucy-icon.ico" - - ; Shortcuts - CreateDirectory "$SMPROGRAMS\${MyAppName}" - CreateShortcut "$SMPROGRAMS\${MyAppName}\${MyAppName}.lnk" "$INSTDIR\${MyAppExeName}" "" "$INSTDIR\${MyAppExeName}" - CreateShortcut "$DESKTOP\${MyAppName}.lnk" "$INSTDIR\${MyAppExeName}" "" "$INSTDIR\${MyAppExeName}" - - ; Add/Remove Programs entry (per-user) - WriteUninstaller "$INSTDIR\Uninstall.exe" - WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${MyAppName}" "DisplayName" "${MyAppName}" - WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${MyAppName}" "DisplayVersion" "${MyAppVersion}" - WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${MyAppName}" "Publisher" "${MyAppPublisher}" - WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${MyAppName}" "DisplayIcon" "$INSTDIR\${MyAppExeName}" - WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${MyAppName}" "UninstallString" "$\"$INSTDIR\Uninstall.exe$\"" - WriteRegDWORD HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${MyAppName}" "NoModify" 1 - WriteRegDWORD HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${MyAppName}" "NoRepair" 1 - - ; Verify prerequisites before the long-running install. - SetOutPath "$INSTDIR" - StrCpy $0 "$\"$INSTDIR\${MyAppExeName}$\" --cli check-prereqs" - ${If} $DeveloperInstall == "1" - StrCpy $0 "$0 --developer" - ${EndIf} - DetailPrint "Checking prerequisites..." - nsExec::ExecToLog $0 - Pop $1 - ${If} $1 != 0 - Call ShowPrerequisitesFailed - DetailPrint "Prerequisites not met — skipped workspace install." - Goto done - ${EndIf} - - ; Build the install command line. - StrCpy $2 "$\"$INSTDIR\${MyAppExeName}$\" --cli $InstallMode --repos-branch master --lucy-ws-ref $SelectedRef --lucy-ws-ref-type $SelectedRefType" - ${If} $DeveloperInstall == "1" - StrCpy $2 "$2 --developer" - ${EndIf} - ${If} $RefreshWorkspace == "1" - StrCpy $2 "$2 --refresh-workspace" - ${EndIf} - - DetailPrint "Running $InstallMode (cloning repos + pixi install + colcon build; this can take a while)..." - nsExec::ExecToLog $2 - Pop $3 - ${If} $3 == 0 - StrCpy $InstallOk "1" - DetailPrint "Install completed successfully." - ${Else} - MessageBox MB_OK|MB_ICONEXCLAMATION "Install finished with errors (exit code $3). See the log above for details." - ${EndIf} - - done: -SectionEnd - -Function LaunchLucy - ${If} $InstallOk == "1" - SetOutPath "$INSTDIR" - Exec '"$INSTDIR\${MyAppExeName}"' - ${EndIf} -FunctionEnd - -; ---------------------------------------------------------------------------- -; Uninstall -; ---------------------------------------------------------------------------- -Section "Uninstall" - Delete "$SMPROGRAMS\${MyAppName}\${MyAppName}.lnk" - RMDir "$SMPROGRAMS\${MyAppName}" - Delete "$DESKTOP\${MyAppName}.lnk" - DeleteRegKey HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${MyAppName}" - RMDir /r "$INSTDIR" -SectionEnd diff --git a/windows/releases.json b/windows/releases.json deleted file mode 100644 index b509b70..0000000 --- a/windows/releases.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "generated_by": "windows/generate_releases.py", - "releases": [ - { - "id": "latest", - "label": "Latest (master)", - "ref": "master", - "ref_type": "branch", - "recommended": true - } - ] -} diff --git a/windows/releases.nsh b/windows/releases.nsh deleted file mode 100644 index 9401f31..0000000 --- a/windows/releases.nsh +++ /dev/null @@ -1,5 +0,0 @@ -; Auto-generated by windows/generate_releases.py — do not edit. -!define LUCY_RELEASES_INCLUDED -!macro LUCY_ADD_RELEASES HWND - ${NSD_CB_AddString} ${HWND} "Latest (master)" -!macroend