From 8fb1f2bc9eba5e82b92997f545ee071bad9e8550 Mon Sep 17 00:00:00 2001 From: Arcod7 Date: Wed, 2 Sep 2026 11:25:35 +0200 Subject: [PATCH 1/4] feat(launcher): make python3 Lucy.py the entry point on Windows too Lucy.py imported curses at module level, so on Windows it died before doing anything. There is also no tmux there to drive the TUI. Import curses lazily, and on Windows run install.py when the workspace is not built, then print the pixi tasks to start by hand. install.py runs with the terminal attached so its pixi and MSVC prompts are answerable. is_installed() also checks install/setup.bat, which is what colcon writes on Windows. --- Lucy.py | 49 +++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 47 insertions(+), 2 deletions(-) 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() From 9a02c018dadc9ce7f910e4ef8ff1206129eb958d Mon Sep 17 00:00:00 2001 From: Arcod7 Date: Wed, 2 Sep 2026 11:44:35 +0200 Subject: [PATCH 2/4] chore(windows): drop generated artifacts, the ps1 wrapper and dead re-exports releases.json and releases.nsh are written by generate_releases.py, which both build paths run first, so tracking them only commits an artifact that drifts. Gitignore them instead. build_installer.ps1 is referenced by nothing in CI and duplicates the steps CI already runs, in PowerShell. The Windows README now lists those steps directly, so there is no wrapper to fall out of sync. install_ops re-exported 25 names from install.py; install_runner.py, Lucy.py and CI together reach for 8. Keep those. --- .gitignore | 6 +++- windows/README.md | 10 ++++-- windows/build_installer.ps1 | 61 ------------------------------------- windows/install_ops.py | 20 +----------- windows/releases.json | 12 -------- windows/releases.nsh | 5 --- 6 files changed, 14 insertions(+), 100 deletions(-) delete mode 100644 windows/build_installer.ps1 delete mode 100644 windows/releases.json delete mode 100644 windows/releases.nsh diff --git a/.gitignore b/.gitignore index 4151621..a55f9c8 100644 --- a/.gitignore +++ b/.gitignore @@ -47,4 +47,8 @@ dist/ *.spec *.local -.local/ \ No newline at end of file +.local/ + +# Generated at installer build time by windows/generate_releases.py +windows/releases.json +windows/releases.nsh diff --git a/windows/README.md b/windows/README.md index d24d68e..c31d534 100644 --- a/windows/README.md +++ b/windows/README.md @@ -88,10 +88,16 @@ Lucy.exe --cli install --developer --refresh-workspace --lucy-ws-ref v1.0.0 --lu Requires [NSIS](https://nsis.sourceforge.io/Download) and PyInstaller: ```powershell -powershell -ExecutionPolicy Bypass -File windows/build_installer.ps1 +python windows/generate_releases.py +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 +& "${env:ProgramFiles(x86)}\NSIS\makensis.exe" "/DMyAppVersion=0.0.0-dev" windows/installer/Lucy.nsi ``` -Outputs `dist\Lucy.exe` and `dist\Lucy-Setup-.exe`. +Outputs `dist\Lucy.exe` and `dist\Lucy-Setup-.exe`. These are the steps +CI runs, so there is no wrapper script to drift out of sync with it. ### Application icon 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/install_ops.py b/windows/install_ops.py index ac45f9a..3582064 100644 --- a/windows/install_ops.py +++ b/windows/install_ops.py @@ -31,32 +31,14 @@ import install as _install # noqa: E402 # --- re-exported cross-platform API (implemented in install.py) -------------- +# Only what install_runner.py, windows/Lucy.py and CI actually reach for. 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 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 From e461b1eaa3bf8f9d44e76823add3cf6aeebe2916 Mon Sep 17 00:00:00 2001 From: Arcod7 Date: Wed, 2 Sep 2026 11:56:09 +0200 Subject: [PATCH 3/4] fix(windows): make the Lucy.exe shortcut do something that works MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit With no arguments Lucy.exe handed off to launch_lucy.sh, which on Windows ends at the curses TUI — no curses, no tmux — so the Start Menu and Desktop shortcuts the installer creates have never worked. They point at Lucy.exe with no arguments, and the .exe is the main Windows install path. It now installs when the workspace is missing and otherwise names the pixi tasks, matching what python3 Lucy.py does. The --cli mode Lucy-Setup.exe drives is untouched. Dropping the launcher half also retires run_command, _find_git_bash and the subprocess import. Also corrects a comment claiming windows/Lucy.py reads install_ops directly; it reaches it through install_runner. --- windows/Lucy.py | 137 ++++++++++++++--------------------------- windows/install_ops.py | 2 +- 2 files changed, 47 insertions(+), 92 deletions(-) diff --git a/windows/Lucy.py b/windows/Lucy.py index f2be2d1..399e19f 100644 --- a/windows/Lucy.py +++ b/windows/Lucy.py @@ -1,17 +1,16 @@ -# Windows launcher for the Lucy workspace. +# Windows entry point 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). +# Compiled to Lucy.exe via PyInstaller, and what the Start Menu / Desktop +# shortcuts run. With no arguments it installs the workspace when it is missing +# and otherwise names the pixi tasks. Lucy-Setup.exe drives install/update/ +# repair through 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) +# 2. Workspace installed (run Lucy-Setup.exe first) import os import shutil -import subprocess import sys if sys.platform != "win32": @@ -29,32 +28,11 @@ _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 +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 _workspace_built(): @@ -65,85 +43,62 @@ def _workspace_built(): ) -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"), - ] +def _is_cli_invocation(): + return len(sys.argv) > 1 and (sys.argv[1] == "--cli" or sys.argv[1] in _CLI_MODES) - 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 +def _run_cli(argv=None): + """Install/update/repair — used by Lucy-Setup.exe, not exposed in the default UX.""" + from install_runner import main as install_main + + if argv is None: + argv = [a for a in sys.argv[1:] if a != "--cli"] + return install_main(argv) - return None +def show_status(): + """Default action, i.e. what the Start Menu and Desktop shortcuts run. -def launch_workspace(): - """Start Pixi and attach to the Lucy Control Center launcher.""" + This used to hand off to launch_lucy.sh, which ends at the curses TUI that + Windows has neither curses nor tmux for, so the shortcuts always failed. + """ if not _workspace_built(): - print( - "Workspace not built. Run Lucy-Setup.exe to install or update first.", - file=sys.stderr, - ) - sys.exit(1) + print("Lucy is not installed in this workspace.") + return _run_cli(["install"]) + print(f"Lucy is installed in {PROJECT_ROOT}.") 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) + return 1 + print("\nStart each component in its own terminal:") + for command, purpose in PIXI_TASKS: + print(f" {command:<24} {purpose}") + return 0 -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) +def _wait_before_closing(): + """A shortcut opens its own console, which would close before it is read.""" + if not getattr(sys, "frozen", False) or _is_cli_invocation(): + return + try: + input("\nPress Enter to close.") + except (EOFError, KeyboardInterrupt): + pass if __name__ == "__main__": os.chdir(PROJECT_ROOT) + code = 0 try: - if _is_cli_invocation(): - sys.exit(_run_cli()) - launch_workspace() + code = _run_cli() if _is_cli_invocation() else show_status() except KeyboardInterrupt: print("\nExiting.") except Exception as e: print(f"An unexpected error occurred: {e}", file=sys.stderr) - sys.exit(1) + code = 1 + _wait_before_closing() + sys.exit(code) diff --git a/windows/install_ops.py b/windows/install_ops.py index 3582064..c2abb51 100644 --- a/windows/install_ops.py +++ b/windows/install_ops.py @@ -31,7 +31,7 @@ import install as _install # noqa: E402 # --- re-exported cross-platform API (implemented in install.py) -------------- -# Only what install_runner.py, windows/Lucy.py and CI actually reach for. +# Only what install_runner.py and CI reach for, plus what this module uses. PrerequisiteError = _install.PrerequisiteError DEFAULT_REPOS_BRANCH = _install.DEFAULT_REPOS_BRANCH InstallMode = _install.InstallMode From 5ef2297aa2fb562fffb0efab2cd31bf7e9bea4db Mon Sep 17 00:00:00 2001 From: Arcod7 Date: Wed, 2 Sep 2026 12:22:23 +0200 Subject: [PATCH 4/4] chore: drop the windows/ exe sources and the two jobs that built them MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Lucy.exe is built elsewhere now, so windows/ has no consumer here: Lucy.py, install_runner.py, install_ops.py, generate_releases.py, installer/Lucy.nsi and the icon all existed only to produce Lucy.exe and Lucy-Setup.exe. windows-build-test and build-and-release-windows-exe go with them — every step they ran pointed inside windows/, so they had nothing left to build. pixi-install-build-test is untouched and still covers all six platforms including windows-latest. One fact lived only in windows/README.md and is now in the root README and the developer guide: pixi resolves win-64 on Windows-on-ARM too, because pixi.lock has no win-arm64. --- .github/workflows/install-and-launch.yml | 135 --------- .gitignore | 5 +- README.md | 2 +- docs/developer_lucy_packages.md | 23 +- install.py | 2 +- windows/Lucy.py | 104 ------- windows/README.md | 114 -------- windows/assets/lucy-icon.ico | Bin 97422 -> 0 bytes windows/generate_releases.py | 78 ------ windows/install_ops.py | 241 ---------------- windows/install_runner.py | 146 ---------- windows/installer/Lucy.nsi | 342 ----------------------- 12 files changed, 11 insertions(+), 1181 deletions(-) delete mode 100644 windows/Lucy.py delete mode 100644 windows/README.md delete mode 100644 windows/assets/lucy-icon.ico delete mode 100644 windows/generate_releases.py delete mode 100644 windows/install_ops.py delete mode 100644 windows/install_runner.py delete mode 100644 windows/installer/Lucy.nsi 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 a55f9c8..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 @@ -49,6 +49,3 @@ dist/ *.local .local/ -# Generated at installer build time by windows/generate_releases.py -windows/releases.json -windows/releases.nsh 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 399e19f..0000000 --- a/windows/Lucy.py +++ /dev/null @@ -1,104 +0,0 @@ -# Windows entry point for the Lucy workspace. -# -# Compiled to Lucy.exe via PyInstaller, and what the Start Menu / Desktop -# shortcuts run. With no arguments it installs the workspace when it is missing -# and otherwise names the pixi tasks. Lucy-Setup.exe drives install/update/ -# repair through the hidden --cli mode (see windows/install_runner.py). -# -# PREREQUISITES: -# 1. Pixi — https://pixi.prefix.dev/latest/installation/ -# 2. Workspace installed (run Lucy-Setup.exe first) - -import os -import shutil -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")) - -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 _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 _is_cli_invocation(): - return len(sys.argv) > 1 and (sys.argv[1] == "--cli" or sys.argv[1] in _CLI_MODES) - - -def _run_cli(argv=None): - """Install/update/repair — used by Lucy-Setup.exe, not exposed in the default UX.""" - from install_runner import main as install_main - - if argv is None: - argv = [a for a in sys.argv[1:] if a != "--cli"] - return install_main(argv) - - -def show_status(): - """Default action, i.e. what the Start Menu and Desktop shortcuts run. - - This used to hand off to launch_lucy.sh, which ends at the curses TUI that - Windows has neither curses nor tmux for, so the shortcuts always failed. - """ - if not _workspace_built(): - print("Lucy is not installed in this workspace.") - return _run_cli(["install"]) - - print(f"Lucy is installed in {PROJECT_ROOT}.") - if shutil.which("pixi") is None: - print( - "Missing pixi. Install: https://pixi.prefix.dev/latest/installation/", - file=sys.stderr, - ) - return 1 - - print("\nStart each component in its own terminal:") - for command, purpose in PIXI_TASKS: - print(f" {command:<24} {purpose}") - return 0 - - -def _wait_before_closing(): - """A shortcut opens its own console, which would close before it is read.""" - if not getattr(sys, "frozen", False) or _is_cli_invocation(): - return - try: - input("\nPress Enter to close.") - except (EOFError, KeyboardInterrupt): - pass - - -if __name__ == "__main__": - os.chdir(PROJECT_ROOT) - code = 0 - try: - code = _run_cli() if _is_cli_invocation() else show_status() - except KeyboardInterrupt: - print("\nExiting.") - except Exception as e: - print(f"An unexpected error occurred: {e}", file=sys.stderr) - code = 1 - _wait_before_closing() - sys.exit(code) diff --git a/windows/README.md b/windows/README.md deleted file mode 100644 index c31d534..0000000 --- a/windows/README.md +++ /dev/null @@ -1,114 +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 -python windows/generate_releases.py -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 -& "${env:ProgramFiles(x86)}\NSIS\makensis.exe" "/DMyAppVersion=0.0.0-dev" windows/installer/Lucy.nsi -``` - -Outputs `dist\Lucy.exe` and `dist\Lucy-Setup-.exe`. These are the steps -CI runs, so there is no wrapper script to drift out of sync with it. - -### 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 5dce64a81afe73ee602562a3106e9afa8e7a7e73..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 97422 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 Rp$`B6002ovPDHLkV1gX2pez6Y 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 c2abb51..0000000 --- a/windows/install_ops.py +++ /dev/null @@ -1,241 +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) -------------- -# Only what install_runner.py and CI reach for, plus what this module uses. -PrerequisiteError = _install.PrerequisiteError -DEFAULT_REPOS_BRANCH = _install.DEFAULT_REPOS_BRANCH -InstallMode = _install.InstallMode - -git_available = _install.git_available -check_prerequisites = _install.check_prerequisites -print_prerequisite_report = _install.print_prerequisite_report - -_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