GuixPkgs translates selected GNU Guix package graphs into checked-in Nix expressions. Nix evaluates them without Import From Derivation (IFD) or a running Guix daemon.
Guix and Nix share the exact same underlying deployment model, but they are built on different languages (Guile Scheme vs. the Nix expression language). While it is technically possible to evaluate Guix packages within Nix using IFD (Import From Derivation), IFD is notoriously slow, breaks evaluation caching, and is forbidden in pure evaluation modes (such as standard Nix flakes).
We want to allow users to transparently mix and match nixpkgs and Guix packages within the same Nix flake, retaining fast evaluation and full reproducibility.
The goal of this repository is to act as a direct, automated translation layer:
- Translate the dependency closures of selected Guix packages at a given commit into a deduplicated set of
.nixfiles. - Provide a standard Nixpkgs-like interface (
packages.x86_64-linux.<package>) via a flake, mapping names to translated packages wrapped with Guix-generated runtime environments. - Automatically sync and translate the upstream Guix tree on a recurring schedule (e.g. via GitHub Actions), recording the exact Guix state in
guix-metadata.json.
GuixPkgs leverages guix-transfer to perform a one-time conversion per Guix commit. The resulting repository tree looks like this:
guixpkgs/
├── flake.nix # The entry point exposing the Guix packages as Nix outputs
├── guix-metadata.json # Tracks the Guix channel, commit, and sync timestamp
├── .github/
│ └── workflows/
│ └── sync.yml # GitHub Action to periodically sync with upstream Guix
└── pkgs/
├── by-name/ # Human-readable entry points wrapping Guix packages
│ ├── h/hello.nix # e.g., `{ pkgs }: pkgs.callPackage ../../wrap-guix-package.nix { … }`
│ └── z/zile.nix
├── store/ # The deduplicated translated Nix derivations, each a
│ ├── b6x8v...-hello.nix # standalone `builtins.derivation { … }`
│ ├── c7y9w...-hello-runtime-env.nix # the package's generated `etc/profile`
│ └── ...
├── sources/ # Local files (patches, builder scripts) referenced by the derivations
└── wrap-guix-package.nix # Generic runtime wrapper for translated packages
Each store/*.nix file is a self-contained builtins.derivation that references
its dependencies directly via (import ../store/<file>.nix).<output>. Public
by-name entries don't expose that raw derivation; instead each is a small
{ pkgs } function that wraps the translated package and sources a companion
*-runtime-env derivation's etc/profile before running any program. That
runtime environment is generated by Guix's own search-path machinery over the
package and its target runtime inputs, so tools work outside a Guix profile —
without unioning those inputs into the visible package output.
The wrapper keeps both halves reachable through passthru: pkg.unwrapped is
the raw translated main output and pkg.runtimeEnv is the generated profile.
Packages that can serve as login shells also expose a package-relative
pkg.shellPath; Bash currently reports /bin/bash. Some packages need small,
Nix-specific build adjustments; those are applied during translation by
guix-transfer, not in this repo — see Patching packages.
You can manually trigger the translation of Guix derivations into Nix expressions at any time. The flake exposes an app to run the synchronization process:
# Note: This requires `guix` to be installed and the Guix daemon running.
nix run .#syncPackage specifications are listed in %package-names in
get-all-derivations.scm. Guix variables that are not package specifications
belong in %aliased-packages, which preserves their requested public names.
After the sync completes, add the generated package tree and metadata so Nix can evaluate them:
git add pkgs/ guix-metadata.jsonTo avoid building Guix packages from source, you can use the provided Cachix binary cache. You can configure this by adding the following nixConfig to your flake.nix:
nixConfig = {
extra-substituters = [ "https://guixpkgs.cachix.org" ];
extra-trusted-public-keys = [ "guixpkgs.cachix.org-1:rM4xwCs5NUy+FcCKkiWP/CmRaSVxxDPaKWZvM1bRopg=" ];
};Alternatively, you can temporarily use it via the command line:
nix build .#hello \
--extra-substituters https://guixpkgs.cachix.org \
--extra-trusted-public-keys guixpkgs.cachix.org-1:rM4xwCs5NUy+FcCKkiWP/CmRaSVxxDPaKWZvM1bRopg=Because GuixPkgs translates Guix packages into pure Nix expressions, they become standard Nix derivations (technically, they evaluate to an attribute set created by builtins.derivation { ... }).
In your own projects, you can mix and match nixpkgs and guixpkgs seamlessly via flakes:
{
description = "A project mixing Nix and Guix";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
guixpkgs.url = "github:fzakaria/guixpkgs";
};
outputs = { self, nixpkgs, guixpkgs }:
let
system = "x86_64-linux";
pkgs = nixpkgs.legacyPackages.${system};
guixPkgs = guixpkgs.packages.${system};
in {
devShells.${system}.default = pkgs.mkShell {
buildInputs = [
pkgs.git # From Nixpkgs
guixPkgs.hello # From Guix (via GuixPkgs)
];
};
};
}Run standard Nix commands against Guix packages effortlessly:
Note
Building Guix packages from source within Nix can sometimes fail during bootstrap (e.g., chmod: Operation not permitted). This is often because Guix's bootstrap scripts try to restore SETGID bits, which the Nix sandbox strictly blocks. To successfully build from source, you may need to disable syscall filtering and/or the sandbox temporarily.
# Build the GNU Hello package from Guix
nix build .#hello --option filter-syscalls false --option sandbox false
# Drop into a shell with Zile from Guix
nix shell .#zile --option filter-syscalls false --option sandbox falsepkgs/store, pkgs/sources, and pkgs/by-name are generated and replaced on
every sync. Do not edit them by hand. Some packages need small, Nix-specific
adjustments because the Guix and Nix build sandboxes differ.
All such fixes are applied at translation time, in guix-transfer — never as a Nix overlay in this repo.
Guix bakes absolute dependency store paths into each package's builder script
(the Guile %build-inputs):
("perl" . "/nix/store/h0gbcdg…-perl-boot0-5.36.0")Those builder scripts are frozen source files. If you change a package's
derivation in a way that alters its output hash (disabling tests, rewriting
the builder, dropping an attribute), every consumer's builder still points at the
old path — so the dependency vanishes from the consumer's PATH at build time
(classic symptom: configure: error: perl not found). A Nix overlay can't fix
this because it can't rewrite the frozen builders. The change has to happen where
builders and dependency edges are emitted together: during translation.
guix-transfer applies these fixes before any path is hashed, so the modified build and all downstream references stay consistent:
| Problem | Handled by |
|---|---|
| Bootstrap test suites fail under Nix's sandbox | guix-transfer --disable-tests rewrites #:tests? #t → #:tests? #f in every *-builder script. The nix run .#sync app passes this flag. |
| An installer applies setuid or setgid modes to a Nix store output | guix-transfer disables those modes beside the builder's existing install-time ownership guard before paths are hashed. |
disallowedReferences (etc.) point at an untranslated /gnu/store path, which Nix rejects as an illegal reference specifier |
guix-transfer filters invalid specifiers out of the allowed/disallowed References/Requisites attributes. |
If a new package needs a tweak, add it to guix-transfer (e.g. a builder
substitution applied in stage_source) and re-sync — there is deliberately no
overlay layer to reach for.