Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NixOS module support: Separate NUR nixpkgs from repos nixpkgs, avoid callPackages #27

Merged
merged 3 commits into from
Jul 20, 2018
Merged
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
116 changes: 80 additions & 36 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ more decentralized way.
NUR automatically check its list of repositories and perform evaluation checks
before it propagated the updates.

## How to use
## Installation

First include NUR in your `packageOverrides`:

Expand All @@ -23,9 +23,9 @@ To make NUR accessible for your login user, add the following to `~/.config/nixp
```nix
{
packageOverrides = pkgs: {
nur = pkgs.callPackage (import (builtins.fetchGit {
url = "https://github.com/nix-community/NUR";
})) {};
nur = import (builtins.fetchTarball "https://github.com/nix-community/NUR/archive/master.tar.gz") {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry I missed why using callPackage was an issue. Both are fine for me

Copy link
Contributor Author

@infinisil infinisil Jul 20, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because when you do pkgs.callPackage, this needs to evaluate pkgs (to get the attribute), which requires evaluating all overlays and the config (because they're passed to the nixpkgs import), but those overlays can be defined in all modules. So if you want to imports = [ nur.repos.infinisil.modules.foo ], this gives infinite recursion, because to evaluate the modules it needs to evaluate pkgs!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, that makes a lot of sense

inherit pkgs;
};
};
}
```
Expand All @@ -35,13 +35,28 @@ For NixOS add the following to your `/etc/nixos/configuration.nix`:
```nix
{
nixpkgs.config.packageOverrides = pkgs: {
nur = pkgs.callPackage (import (builtins.fetchGit {
url = "https://github.com/nix-community/NUR";
})) {};
nur = import (builtins.fetchTarball "https://github.com/nix-community/NUR/archive/master.tar.gz") {
inherit pkgs;
};
};
}
```

### Pinning

Using `builtins.fetchTarball` without a sha256 will only cache the download for 1 hour by default, so you need internet access almost every time you build something. You can pin the version if you don't want that:
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This section is new

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update I have not yet understand the full consequence of a fixed-input derivation: I have a non-stable URL in configuration.nix that I gave a checksum and nix is testing it from time to time and complains if the checksum changes. On the other hand I never had evaluation errors because the file is not available. Does this only works in my case because the referenced file is part of system profile closure or does this also apply to imported nix expression?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For fixed output derivations (ones you give a hash), Nix will first check if a file with that hash is already in the store, and use that if there is. It will only try to fetch it when no such hash could be found. After the fetch it will only succeed in adding the download if the hash you gave matches the one it calculated. If it doesn't match, it fails. The fetchers from nixpkgs will also output the correct hash on a mismatch.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does nix decide, when to delete fixed-input derivations in this case?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the only way it deletes them is garbage collection.


```nix
builtins.fetchTarball {
# Get the revision by choosing a version from https://github.com/nix-community/NUR/commits/master
url = "https://github.com/nix-community/NUR/archive/3a6a6f4da737da41e27922ce2cfacf68a109ebce.tar.gz";
# Get the hash by running `nix-prefetch-url --unpack <url>` on the above url
sha256 = "04387gzgl8y555b3lkz9aiw9xsldfg4zmzp930m62qw8zbrvrshd";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Future work (not in the scope of this pr) - automate pinning updates.

}
```

## How to use

Then packages can be used or installed from the NUR namespace.

```console
Expand All @@ -62,7 +77,7 @@ or

```console
# configuration.nix
environment.systemPackages = [
environment.systemPackages = with pkgs; [
nur.repos.mic92.inxi
];
```
Expand All @@ -73,19 +88,39 @@ for its content.
***NUR does not check repository for malicious content on a regular base and it is
recommend to check expression before installing them.***

### Using modules overlays or library functions on NixOS

If you intend to use modules, overlays or library functions in your NixOS configuration.nix, you need to take care to not introduce infinite recursion. Specifically, you need to import NUR like this in the modules:

```nix
{ pkgs, config, lib, ... }:
let
nur-no-pkgs = import (builtins.fetchTarball "https://github.com/nix-community/NUR/archive/master.tar.gz") {};
in {

imports = [
nur-no-pkgs.repos.paul.modules.foo
];

nixpkgs.overlays = [
nur-no-pkgs.repos.ben.overlays.bar
];

}
```

## How to add your own repository.

First create a repository that contains a `default.nix` in its top-level directory.

DO NOT import packages for example `with import <nixpkgs> {};`.
Instead take all dependency you want to import from Nixpkgs by function arguments.
Instead take all dependency you want to import from Nixpkgs from the given `pkgs` argument.
Each repository should return a set of Nix derivations:

```nix
{ callPackage }:
{ pkgs }:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might be a bit more convenient for nix-build/nix-shell:

{ pkgs ? import <nixpkgs> {} }:

Nix-shell would then shorten too:

$ nix-shell -E '(import ./default.nix {}).inxi'

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also this might encourage people again to import nixpkgs themselves?

Copy link
Contributor Author

@infinisil infinisil Jul 16, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't matter as long as import <nixpkgs> {} is only used as the default value, since NUR will pass its own pkgs to override it. (I added a section for that in the newest commit).

{
inxi = callPackage ./inxi {};
inxi = pkgs.callPackage ./inxi {};
}
```

Expand Down Expand Up @@ -135,13 +170,26 @@ in stdenv.mkDerivation rec {
You can use `nix-shell` or `nix-build` to build your packages:

```console
$ nix-shell -E 'with import <nixpkgs>{}; (callPackage ./default.nix {}).inxi'
$ nix-shell --arg pkgs 'import <nixpkgs> {}' -A inxi
nix-shell> inxi
nix-shell> find $buildInputs
```

```console
$ nix-build -E 'with import <nixpkgs>{}; (callPackage ./default.nix {})'
$ nix-build --arg pkgs 'import <nixpkgs> {}' -A inxi
```

For development convenience, you can also set a default value for the pkgs argument:

```nix
{ pkgs ? import <nixpkgs> {} }:
{
inxi = pkgs.callPackage ./inxi {};
}
```

```console
$ nix-build -A inxi
```

Add your own repository to in the `repos.json` of NUR:
Expand Down Expand Up @@ -181,7 +229,7 @@ and open a pull request towards [https://github.com/nix-community/NUR](https://g
At the moment repositories should be buildable on Nixpkgs unstable. Later we
will add options to also provide branches for other Nixpkgs channels.

## Use a different nix file as root expression
### Use a different nix file as root expression

To use a different file instead of `default.nix` to load packages from, set the `file`
option to a path relative to the repository root:
Expand All @@ -197,7 +245,7 @@ option to a path relative to the repository root:
}
```

## Update NUR's lock file after updating your repository
### Update NUR's lock file after updating your repository

By default we only check for repository updates once a day with an automatic
cron job in travis ci to update our lock file `repos.json.lock`.
Expand All @@ -210,7 +258,7 @@ curl -XPOST https://nur-update.herokuapp.com/update?repo=mic92

Check out the [github page](https://github.com/nix-community/nur-update#nur-update-endpoint) for further details

## Git submodules
### Git submodules

To fetch git submodules in repositories set `submodules`:

Expand All @@ -225,49 +273,43 @@ To fetch git submodules in repositories set `submodules`:
}
```

<!--
This currently does not work as advertised at least for modules
### NixOS modules, overlays and library function support

## Conventions for NixOS modules, overlays and library functions
It is also possible to define more than just packages. In fact any Nix expression can be used.

To make NixOS modules, overlays and library functions more discoverable,
we propose to put them in their own namespace within the repository.
This allows us to make them later searchable, when the indexer is ready.

Put all NixOS modules in the `modules` attribute of your repository:

#### Providing NixOS modules

NixOS modules should be placed in the `modules` attribute:

```nix
# default.nix
{
modules = ./import modules;
{ pkgs }: {
modules = import ./modules;
}
```

```nix
# modules/default.nix
{
example-module = ./import example-module.nix;
example-module = import ./example-module.nix;
}
```

An example can be found [here](https://github.com/Mic92/nur-packages/tree/master/modules).

The resulting module can be then added to `imports = [];` within `configuration.nix`:

```nix
# /etc/nixos/configuration.nix
{...}: {
imports = [ nur.repos.mic92.modules.transocks ];
}
```
#### Providing Overlays

For overlays use the `overlays` attribute:

```nix
# default.nix
{
overlays = {
hello-overlay = ./import hello-overlay;
hello-overlay = import ./hello-overlay;
};
}
```
Expand All @@ -293,11 +335,13 @@ The result can be used like this:
}
```

#### Providing library functions

Put reusable nix functions that are intend for public use in the `lib` attribute:

```nix
{ lib }:
with lib;
{ pkgs }:
with pkgs.lib;
{
lib = {
hexint = x: hexvals.${toLower x};
Expand All @@ -307,7 +351,7 @@ with lib;
};
}
```
-->


## Contribution guideline

Expand Down
13 changes: 9 additions & 4 deletions default.nix
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
{ nurpkgs ? import <nixpkgs> {} # For nixpkgs dependencies used by NUR itself
# Dependencies to call NUR repos with
, pkgs ? null }:

{ pkgs ? import <nixpkgs> {} }:
let
inherit (pkgs) fetchgit fetchzip callPackages lib;
inherit (nurpkgs) fetchgit fetchzip lib;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe add a note that the builtins have issues with TTL

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably not at this line of code, but yeah I can add that to the readme :)

Copy link
Member

@Mic92 Mic92 Jul 18, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is possible to prevent garbage collection by linking used tarballs in a profile?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think so


manifest = (builtins.fromJSON (builtins.readFile ./repos.json)).repos;
lockedRevisions = (builtins.fromJSON (builtins.readFile ./repos.json.lock)).repos;
Expand Down Expand Up @@ -40,9 +42,12 @@ let
fetchSubmodules = submodules;
};

expressionPath = name: attr: (repoSource name attr) + "/" + (attr.file or "");
createRepo = name: attr: import ./lib/evalRepo.nix {
inherit name pkgs lib;
inherit (attr) url;
src = repoSource name attr + "/" + (attr.file or "");
};

createRepo = (name: attr: callPackages (expressionPath name attr) {});
in {
repos = lib.mapAttrs createRepo manifest;
repo-sources = lib.mapAttrs repoSource manifest;
Expand Down
34 changes: 34 additions & 0 deletions lib/evalRepo.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{ name
, url
, src
, pkgs # Do not use this for anything other than passing it along as an argument to the repository
, lib
}:
let

prettyName = "${name}";

# Arguments passed to each repositories default.nix
passedArgs = {
pkgs = if pkgs != null then pkgs else throw ''
NUR import call didn't receive a pkgs argument, but the evaluation of NUR's ${prettyName} repository requires it.

This is either because
- You're trying to use a package from that repository, but didn't pass a `pkgs` argument to the NUR import.
In that case, refer to the installation instructions at https://github.com/nix-community/nur#installation on how to properly import NUR

- You're trying to use a module/overlay from that repository, but it didn't properly declare their module.
In that case, inform the maintainer of the repository: ${url}
'';
};

expr = import src;
args = builtins.functionArgs expr;
# True if not all arguments are either passed by default (e.g. pkgs) or defaulted (e.g. foo ? 10)
usesCallPackage = ! lib.all (arg: lib.elem arg (lib.attrNames passedArgs) || args.${arg}) (lib.attrNames args);

in if usesCallPackage then lib.warn ''
NUR repository ${prettyName} is using the deprecated callPackage syntax which
might result in infinite recursion when used with NixOS modules.
'' (passedArgs.pkgs.callPackages src {})
else expr (builtins.intersectAttrs args passedArgs)
11 changes: 9 additions & 2 deletions nur/update.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@
from enum import Enum, auto
from urllib.parse import urlparse, urljoin, ParseResult

ROOT = Path(__file__).parent.parent
ROOT = Path(__file__).parent.parent.resolve();
LOCK_PATH = ROOT.joinpath("repos.json.lock")
MANIFEST_PATH = ROOT.joinpath("repos.json")
EVALREPO_PATH = ROOT.joinpath("lib/evalRepo.nix")

Url = ParseResult

Expand Down Expand Up @@ -214,7 +215,12 @@ def eval_repo(spec: RepoSpec, repo_path: Path) -> None:
with open(eval_path, "w") as f:
f.write(f"""
with import <nixpkgs> {{}};
callPackages {repo_path.joinpath(spec.nix_file)} {{}}
import {EVALREPO_PATH} {{
name = "{spec.name}";
url = "{spec.url}";
src = {repo_path.joinpath(spec.nix_file)};
inherit pkgs lib;
}}
""")

cmd = [
Expand All @@ -229,6 +235,7 @@ def eval_repo(spec: RepoSpec, repo_path: Path) -> None:
"-I", f"nixpkgs={nixpkgs_path()}",
"-I", str(repo_path),
"-I", str(eval_path),
"-I", str(EVALREPO_PATH),
] # yapf: disable

print(f"$ {' '.join(cmd)}")
Expand Down