Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
135 changes: 0 additions & 135 deletions .github/workflows/install-and-launch.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
5 changes: 3 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,13 @@ 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
dist/
*.spec

*.local
.local/
.local/

49 changes: 47 additions & 2 deletions Lucy.py
Original file line number Diff line number Diff line change
@@ -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"):
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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()
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
23 changes: 8 additions & 15 deletions docs/developer_lucy_packages.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`:

Expand Down Expand Up @@ -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`)

Expand Down Expand Up @@ -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 |
|-----------------------|---------|
Expand Down Expand Up @@ -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 |
2 changes: 1 addition & 1 deletion install.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
Loading