mirror of
https://github.com/open-jarvis/OpenJarvis.git
synced 2026-08-14 00:47:52 +00:00
fix(packaging): make openjarvis-rust a uv-only group to unblock pip install openjarvis[desktop] (#624)
* fix(packaging): make openjarvis-rust a uv-only dependency group (unblock pip[desktop]) #615 added openjarvis-rust to the published `desktop` extra so `uv sync --extra desktop` builds the native PyO3 extension for the desktop app. But openjarvis-rust is not on PyPI and `[tool.uv.sources]` is stripped from published wheel metadata, so `pip install openjarvis[desktop]` from PyPI failed at install trying to resolve openjarvis-rust from PyPI (#584). Move openjarvis-rust into a uv `desktop-native` dependency group (PEP 735 — excluded from wheel metadata) and sync it in the desktop app via `uv sync --group desktop-native`. The extension is still built from the local path source; only the published metadata changes. Verified: the built wheel no longer lists openjarvis-rust in any Requires-Dist (nowhere in the metadata), and uv.lock still resolves it from the local path source under the group. Adds tests/deployment/test_packaging.py to guard the split (not in the published extra, present in the group, path source, and the desktop app syncs the group). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(packaging): sync native group in install paths --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: Elliot Slusky <elliot@slusky.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
Elliot Slusky
parent
f3954e087a
commit
26bc7efb09
@@ -20,8 +20,8 @@ What it does:
|
||||
4. Installs `uv` (https://astral.sh/uv) if absent.
|
||||
5. Clones the OpenJarvis repository to `%LOCALAPPDATA%\OpenJarvis`
|
||||
(override with `$env:OPENJARVIS_HOME`).
|
||||
6. Runs `uv sync --extra desktop` so the FastAPI server and speech backend are
|
||||
importable.
|
||||
6. Runs `uv sync --extra desktop --group desktop-native` so the FastAPI server,
|
||||
speech backend, and native extension are importable.
|
||||
7. Optionally prompts to register a scheduled task that auto-starts the
|
||||
server at logon.
|
||||
|
||||
@@ -105,7 +105,7 @@ To pull the latest:
|
||||
```powershell
|
||||
cd "$env:LOCALAPPDATA\OpenJarvis\src"
|
||||
git pull --ff-only
|
||||
uv sync --extra desktop
|
||||
uv sync --extra desktop --group desktop-native
|
||||
```
|
||||
|
||||
Or re-run the installer with `-Force`:
|
||||
|
||||
@@ -16,8 +16,8 @@
|
||||
4. Install uv (https://astral.sh/uv) if absent.
|
||||
5. Clone the OpenJarvis repository to $env:LOCALAPPDATA\OpenJarvis
|
||||
(override with $env:OPENJARVIS_HOME).
|
||||
6. Run `uv sync --extra desktop` so the FastAPI server and speech
|
||||
backend are importable.
|
||||
6. Run `uv sync --extra desktop --group desktop-native` so the FastAPI
|
||||
server, speech backend, and native extension are importable.
|
||||
7. Optionally register the scheduled-task service (see
|
||||
deploy/windows/jarvis-service.ps1).
|
||||
|
||||
@@ -279,13 +279,13 @@ if (Test-Path (Join-Path $srcDir '.git')) {
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 6. uv sync --extra desktop
|
||||
# 6. uv sync --extra desktop --group desktop-native
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
Write-Info "Running 'uv sync --extra desktop' in $srcDir (this can take a few minutes)..."
|
||||
Write-Info "Running 'uv sync --extra desktop --group desktop-native' in $srcDir (this can take a few minutes)..."
|
||||
Push-Location $srcDir
|
||||
try {
|
||||
& $uvExe sync --extra desktop
|
||||
& $uvExe sync --extra desktop --group desktop-native
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Fail "uv sync failed with exit code $LASTEXITCODE. Check the output above."
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ avoid a Linux VM; WSL2 remains the smoother experience for most users.
|
||||
## What you get
|
||||
|
||||
- A PowerShell installer that probes prerequisites, installs `uv`,
|
||||
clones the repo, and runs `uv sync --extra desktop`.
|
||||
clones the repo, and runs `uv sync --extra desktop --group desktop-native`.
|
||||
- An optional Windows scheduled-task service equivalent to the systemd
|
||||
unit and launchd plist.
|
||||
- Loopback default — the service binds `127.0.0.1` so no API key is
|
||||
@@ -38,7 +38,7 @@ The installer will:
|
||||
4. Install `uv` if absent (via the official `astral.sh/uv` PowerShell
|
||||
installer).
|
||||
5. Clone the repo to `%LOCALAPPDATA%\OpenJarvis\src`.
|
||||
6. Run `uv sync --extra desktop`.
|
||||
6. Run `uv sync --extra desktop --group desktop-native`.
|
||||
7. Prompt to register the scheduled-task service (skip with
|
||||
`-SkipService`).
|
||||
|
||||
|
||||
@@ -8,6 +8,8 @@ use tokio::sync::Mutex;
|
||||
|
||||
const OLLAMA_PORT: u16 = 11434;
|
||||
const JARVIS_PORT: u16 = 8000;
|
||||
const DESKTOP_UV_SYNC_COMMAND: &str =
|
||||
"uv sync --extra desktop --extra inference-cloud --extra inference-google --group desktop-native";
|
||||
|
||||
/// Small, fast model used when startup needs a default Ollama tag.
|
||||
const STARTUP_MODEL: &str = "qwen3.5:4b";
|
||||
@@ -740,10 +742,11 @@ fn format_uv_sync_failure(
|
||||
format!(
|
||||
"`uv sync` failed in {} (exit {}). Last output:\n\n{}\n\n\
|
||||
Try opening a terminal in that directory and running \
|
||||
`uv sync --extra desktop` manually for the full output.{}",
|
||||
`{}` manually for the full output.{}",
|
||||
root.display(),
|
||||
code,
|
||||
tail,
|
||||
DESKTOP_UV_SYNC_COMMAND,
|
||||
rust_hint,
|
||||
)
|
||||
}
|
||||
@@ -829,7 +832,7 @@ fn format_extension_import_failure(root: &std::path::Path, stderr: &str) -> Stri
|
||||
"`openjarvis_rust` is still not importable after building. Last output:\n\n{}\n\n\
|
||||
Run these manually for the full build log:\n\n\
|
||||
cd {}\n\
|
||||
uv sync --extra desktop\n\
|
||||
{}\n\
|
||||
uv run python -c \"import openjarvis_rust\"",
|
||||
if tail.is_empty() {
|
||||
"(no stderr output)"
|
||||
@@ -837,6 +840,7 @@ fn format_extension_import_failure(root: &std::path::Path, stderr: &str) -> Stri
|
||||
&tail
|
||||
},
|
||||
root.display(),
|
||||
DESKTOP_UV_SYNC_COMMAND,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1350,6 +1354,9 @@ async fn boot_backend(backend: SharedBackend, status: SharedStatus) {
|
||||
"--extra", "desktop",
|
||||
"--extra", "inference-cloud",
|
||||
"--extra", "inference-google",
|
||||
// openjarvis_rust lives in a uv dependency group (not the published
|
||||
// `desktop` extra) so pip installs from PyPI don't require it (#584).
|
||||
"--group", "desktop-native",
|
||||
])
|
||||
.stdout(std::process::Stdio::null())
|
||||
.stderr(std::process::Stdio::piped())
|
||||
@@ -2866,7 +2873,7 @@ mod tests {
|
||||
format_uv_sync_spawn_error, matching_installed_model, model_names_match, normalize_host,
|
||||
parse_inference_config, parse_ollama_model_names, preferred_installed_model,
|
||||
should_persist_resolved_model, startup_installed_model, upsert_engine_host,
|
||||
uv_sync_stderr_tail, InferenceConfig, SourceKind,
|
||||
uv_sync_stderr_tail, InferenceConfig, SourceKind, DESKTOP_UV_SYNC_COMMAND,
|
||||
};
|
||||
use std::path::Path;
|
||||
|
||||
@@ -2910,7 +2917,7 @@ mod tests {
|
||||
assert!(msg.contains("exit 2"));
|
||||
assert!(msg.contains("/home/u/.openjarvis/src"));
|
||||
assert!(msg.contains("failed to resolve numpy==2.1.3"));
|
||||
assert!(msg.contains("uv sync --extra desktop")); // actionable next step
|
||||
assert!(msg.contains(DESKTOP_UV_SYNC_COMMAND)); // actionable next step
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -2962,7 +2969,7 @@ mod tests {
|
||||
"ModuleNotFoundError: No module named 'openjarvis_rust'",
|
||||
);
|
||||
assert!(msg.contains("openjarvis_rust"));
|
||||
assert!(msg.contains("uv sync --extra desktop"));
|
||||
assert!(msg.contains(DESKTOP_UV_SYNC_COMMAND));
|
||||
assert!(msg.contains("uv run python -c \"import openjarvis_rust\""));
|
||||
assert!(msg.contains("ModuleNotFoundError"));
|
||||
}
|
||||
|
||||
+9
-1
@@ -91,7 +91,6 @@ desktop = [
|
||||
"pydantic>=2.0",
|
||||
"python-multipart>=0.0.9",
|
||||
"faster-whisper>=1.0",
|
||||
"openjarvis-rust",
|
||||
]
|
||||
openhands = ["openhands-sdk>=1.0; python_version >= '3.12'"]
|
||||
gpu-metrics = ["pynvml>=12.0"]
|
||||
@@ -239,3 +238,12 @@ select = ["E", "F", "I", "W"]
|
||||
dev = [
|
||||
"maturin>=1.12.6",
|
||||
]
|
||||
# openjarvis_rust is the native PyO3 extension, built from the local Rust
|
||||
# workspace. It lives in a uv dependency group (PEP 735) — not the published
|
||||
# `desktop` extra — so `uv sync --group desktop-native` builds it from source
|
||||
# for the desktop app, while `pip install openjarvis[desktop]` from PyPI does
|
||||
# NOT try to resolve openjarvis-rust from PyPI, where it isn't published
|
||||
# (dependency groups are excluded from wheel metadata). See #584 / #615.
|
||||
desktop-native = [
|
||||
"openjarvis-rust",
|
||||
]
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
"""Guards for the openjarvis-rust packaging split (#584 / #615).
|
||||
|
||||
``openjarvis_rust`` is the native PyO3 extension. It is NOT published to PyPI,
|
||||
so it must not appear in the published ``desktop`` extra — listing it there
|
||||
breaks ``pip install openjarvis[desktop]`` at install time. It lives in the uv
|
||||
``desktop-native`` dependency group instead (excluded from wheel metadata),
|
||||
which the desktop app installs from source via
|
||||
``uv sync --group desktop-native``.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import tomllib
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent.parent
|
||||
PYPROJECT = ROOT / "pyproject.toml"
|
||||
DESKTOP_LIB_RS = ROOT / "frontend" / "src-tauri" / "src" / "lib.rs"
|
||||
WINDOWS_INSTALL_PS1 = ROOT / "deploy" / "windows" / "install.ps1"
|
||||
|
||||
|
||||
def _pyproject() -> dict:
|
||||
return tomllib.loads(PYPROJECT.read_text())
|
||||
|
||||
|
||||
def test_openjarvis_rust_not_in_published_desktop_extra() -> None:
|
||||
desktop = _pyproject()["project"]["optional-dependencies"]["desktop"]
|
||||
assert not any("openjarvis-rust" in dep for dep in desktop), (
|
||||
"openjarvis-rust must not be in the published `desktop` extra — it is "
|
||||
"not on PyPI, so it breaks `pip install openjarvis[desktop]`."
|
||||
)
|
||||
|
||||
|
||||
def test_openjarvis_rust_lives_in_uv_dependency_group() -> None:
|
||||
group = _pyproject()["dependency-groups"]["desktop-native"]
|
||||
assert any("openjarvis-rust" in dep for dep in group)
|
||||
|
||||
|
||||
def test_openjarvis_rust_has_local_uv_path_source() -> None:
|
||||
src = _pyproject()["tool"]["uv"]["sources"]["openjarvis-rust"]
|
||||
assert src["path"] == "rust/crates/openjarvis-python"
|
||||
|
||||
|
||||
def test_desktop_app_syncs_the_native_group() -> None:
|
||||
# Otherwise the group's openjarvis_rust is never installed for the app.
|
||||
assert '"desktop-native"' in DESKTOP_LIB_RS.read_text(), (
|
||||
"the desktop app must `uv sync --group desktop-native` so the native "
|
||||
"extension is built at launch."
|
||||
)
|
||||
|
||||
|
||||
def test_windows_installer_syncs_the_native_group() -> None:
|
||||
# The Windows source installer does not run maturin separately.
|
||||
assert (
|
||||
"& $uvExe sync --extra desktop --group desktop-native"
|
||||
in WINDOWS_INSTALL_PS1.read_text()
|
||||
), (
|
||||
"the Windows installer must include `--group desktop-native` so "
|
||||
"openjarvis_rust is built during source install."
|
||||
)
|
||||
@@ -4206,7 +4206,6 @@ dashboard = [
|
||||
desktop = [
|
||||
{ name = "fastapi" },
|
||||
{ name = "faster-whisper" },
|
||||
{ name = "openjarvis-rust" },
|
||||
{ name = "pydantic" },
|
||||
{ name = "python-multipart" },
|
||||
{ name = "uvicorn" },
|
||||
@@ -4339,6 +4338,9 @@ tools-search = [
|
||||
]
|
||||
|
||||
[package.dev-dependencies]
|
||||
desktop-native = [
|
||||
{ name = "openjarvis-rust" },
|
||||
]
|
||||
dev = [
|
||||
{ name = "maturin" },
|
||||
]
|
||||
@@ -4391,7 +4393,6 @@ requires-dist = [
|
||||
{ name = "openai", marker = "extra == 'inference-cloud'", specifier = ">=1.30" },
|
||||
{ name = "openai", marker = "extra == 'media'", specifier = ">=1.30" },
|
||||
{ name = "openhands-sdk", marker = "python_full_version >= '3.12' and extra == 'openhands'", specifier = ">=1.0" },
|
||||
{ name = "openjarvis-rust", marker = "extra == 'desktop'", directory = "rust/crates/openjarvis-python" },
|
||||
{ name = "pdfplumber", marker = "extra == 'memory-pdf'", specifier = ">=0.10" },
|
||||
{ name = "pdfplumber", marker = "extra == 'pdf'", specifier = ">=0.10" },
|
||||
{ name = "playwright", marker = "extra == 'browser'", specifier = ">=1.40" },
|
||||
@@ -4445,6 +4446,7 @@ requires-dist = [
|
||||
provides-extras = ["browser", "channel-discord", "channel-gmail", "channel-line", "channel-mastodon", "channel-messenger", "channel-nostr", "channel-reddit", "channel-rocketchat", "channel-slack", "channel-telegram", "channel-twilio", "channel-twitch", "channel-twitter", "channel-viber", "channel-xmpp", "channel-zulip", "dashboard", "desktop", "dev", "docs", "energy-all", "energy-amd", "energy-apple", "eval-sheets", "eval-wandb", "framework-comparison", "gpu-metrics", "inference-cloud", "inference-gemma", "inference-google", "inference-litellm", "inference-mlx", "inference-vllm", "learning-dspy", "learning-gepa", "media", "memory-bm25", "memory-colbert", "memory-faiss", "memory-pdf", "mining-pearl-cpu", "mining-pearl-vllm", "openhands", "orchestrator-training", "pdf", "sandbox-docker", "sandbox-wasm", "scheduler", "security-signing", "server", "speech", "speech-deepgram", "tools-search"]
|
||||
|
||||
[package.metadata.requires-dev]
|
||||
desktop-native = [{ name = "openjarvis-rust", directory = "rust/crates/openjarvis-python" }]
|
||||
dev = [{ name = "maturin", specifier = ">=1.12.6" }]
|
||||
|
||||
[[package]]
|
||||
|
||||
Reference in New Issue
Block a user