Files
hermes-agent/nix/python.nix
ethernet e66e0e7caa fix(packaging): restore nix desktop and docker builds
Generate frontend icons from the shared source artwork during each build.
Keep the locked icon dependencies out of the application runtime.

Nix needs setuptools and CFFI to build the locked python-olm source.
Its offline npm manifest must omit semver overrides because direct
package specs become local tarball paths. Preserve the repository
manifest and lockfile.

Docker must use the PM interpreter rather than a second Python under
/root. Install the application into its venv explicitly, use the image
compiler, and make package facts readable by the runtime user.

Verified:
- nix build .#desktop and frontend-icons/package-contents/cli-commands
- Packaged Electron PTY spawn and libolm encryption roundtrip
- ARM Linux and macOS desktop evaluation, not native builds
- Docker linux/amd64 build and 8 focused runtime tests

The full test suite was not run. Leave llama integration unchanged.
2026-09-10 10:34:39 -04:00

180 lines
5.6 KiB
Nix

# nix/python.nix — uv2nix virtual environment builder
{
lib,
callPackage,
uv2nix,
pyproject-nix,
pyproject-build-systems,
stdenv,
# Filtered Python source (see lib.nix pythonSrc) — keeps JS/docs/skills
# edits from invalidating the venv derivation.
pythonSrc,
dependency-groups ? [ "all" ],
}:
let
# The interpreter family comes from pm/lock.json (pythonLock.nix owns the
# selection); every override below must be built for THAT interpreter.
pythonLock = callPackage ./pythonLock.nix { };
python = pythonLock.interpreter;
pythonPackages = python.pkgs;
workspace = uv2nix.lib.workspace.loadWorkspace { workspaceRoot = pythonSrc; };
hacks = callPackage pyproject-nix.build.hacks { };
overlay = workspace.mkPyprojectOverlay {
sourcePreference = "wheel";
};
isAarch64Darwin = stdenv.hostPlatform.system == "aarch64-darwin";
# Keep the workspace locked through uv2nix, but supply the local voice stack
# from nixpkgs so wheel-only transitive artifacts do not break evaluation.
mkPrebuiltPassthru = dependencies: {
inherit dependencies;
optional-dependencies = { };
dependency-groups = { };
};
mkPrebuiltOverride =
final: from: dependencies:
hacks.nixpkgsPrebuilt {
inherit from;
prev = {
nativeBuildInputs = [ final.pyprojectHook ];
passthru = mkPrebuiltPassthru dependencies;
};
};
# Legacy alibabacloud packages ship only sdists with setup.py/setup.cfg
# and no pyproject.toml, so setuptools isn't declared as a build dep.
buildSystemOverrides =
final: prev:
builtins.mapAttrs
(
name: _:
prev.${name}.overrideAttrs (old: {
nativeBuildInputs = (old.nativeBuildInputs or [ ]) ++ [ final.setuptools ];
})
)
(
lib.genAttrs [
"alibabacloud-credentials-api"
"alibabacloud-endpoint-util"
"alibabacloud-gateway-dingtalk"
"alibabacloud-gateway-spi"
"alibabacloud-tea"
] (_: null)
)
// {
# The locked sdist has no build-system metadata; setup.py imports
# setuptools and uses CFFI to compile the bundled libolm.
python-olm = prev.python-olm.overrideAttrs (old: {
nativeBuildInputs = (old.nativeBuildInputs or [ ]) ++ final.resolveBuildSystem {
setuptools = [ ];
cffi = [ ];
};
});
};
pythonPackageOverrides =
final: _prev:
if isAarch64Darwin then
{
numpy = mkPrebuiltOverride final pythonPackages.numpy { };
pyarrow = mkPrebuiltOverride final pythonPackages.pyarrow { };
av = mkPrebuiltOverride final pythonPackages.av { };
humanfriendly = mkPrebuiltOverride final pythonPackages.humanfriendly { };
coloredlogs = mkPrebuiltOverride final pythonPackages.coloredlogs {
humanfriendly = [ ];
};
onnxruntime = mkPrebuiltOverride final pythonPackages.onnxruntime {
coloredlogs = [ ];
numpy = [ ];
packaging = [ ];
};
ctranslate2 = mkPrebuiltOverride final pythonPackages.ctranslate2 {
numpy = [ ];
pyyaml = [ ];
};
faster-whisper = mkPrebuiltOverride final pythonPackages.faster-whisper {
av = [ ];
ctranslate2 = [ ];
huggingface-hub = [ ];
onnxruntime = [ ];
tokenizers = [ ];
tqdm = [ ];
};
}
else
{ };
pythonSet =
(callPackage pyproject-nix.build.packages {
inherit python;
}).overrideScope
(
lib.composeManyExtensions [
pyproject-build-systems.overlays.default
overlay
buildSystemOverrides
pythonPackageOverrides
# ``setup.py`` permits wheel/sdist creation only from the sealed
# Hermes derivation. This is deliberately a derivation environment
# variable, not a devShell variable: ``nix develop -c uv build``
# must remain blocked.
(final: prev: {
hermes-agent = prev.hermes-agent.overrideAttrs (_old: {
HERMES_NIX_BUILD = "1";
});
})
]
);
# The editable venv points at the live checkout, so it uses an
# UNFILTERED workspace rooted at a real path — mkEditablePyprojectOverlay
# computes relative paths via lib.path.splitRoot, which rejects the
# filtered pythonSrc (a cleanSourceWith set, not a path). Filtering
# buys nothing here anyway: the editable install reads from
# $HERMES_PYTHON_SRC_ROOT at runtime.
workspaceRoot = ./..;
editableWorkspace = uv2nix.lib.workspace.loadWorkspace { inherit workspaceRoot; };
editableOverlay = editableWorkspace.mkEditablePyprojectOverlay {
root = "$HERMES_PYTHON_SRC_ROOT"; # resolved at shellHook time
};
editableSet = pythonSet.overrideScope (
lib.composeManyExtensions [
editableOverlay
(final: prev: {
hermes-agent = prev.hermes-agent.overrideAttrs (old: {
# point straight at the real source instead of the filtered nix store copy
src = workspaceRoot;
nativeBuildInputs = old.nativeBuildInputs ++ final.resolveBuildSystem { editables = [ ]; };
});
})
]
);
in
{
inherit python;
# Equivalent to uv's --only-group: use the lock-derived dependency spec,
# without installing Hermes or its runtime dependencies in the build env.
iconBuildVenv = pythonSet.mkVirtualEnv "hermes-icon-build-env"
pythonSet.hermes-agent.dependency-groups.icon-build;
venv = pythonSet.mkVirtualEnv "hermes-agent-env" {
hermes-agent = dependency-groups;
};
editableVenv = editableSet.mkVirtualEnv "hermes-agent-editable-env" {
hermes-agent = dependency-groups;
};
}