Skip to content

Commit 607be72

Browse files
Migrate from poetry to uv (#22)
* Migrate from `poetry` to `uv` * update nixpkgs to unstable nixpkgs on previously 22.05 changed something in regard to `python.interpreter`. Updating to a version atleast as recent as used in other runtime verification repositories fixes this. * add CLI example * migrate from `poetry2nix` to `uv2nix` * add git submodule ignore code and notice in nix derivation * adjust follow structure of flake inputs * move nix documentation into a README inside the `nix` directory * update nix documentation * change `nixpkgs` from unstable to release 25.05 --------- Co-authored-by: Julian Kuners <julian.kuners@gmail.com>
1 parent 601d5e2 commit 607be72

File tree

9 files changed

+226
-94
lines changed

9 files changed

+226
-94
lines changed

.github/workflows/test-pr.yml

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,19 +14,19 @@ jobs:
1414
- name: 'Check out code'
1515
uses: actions/checkout@v3
1616
- name: 'Install Python'
17-
uses: actions/setup-python@v4
17+
uses: actions/setup-python@v5
1818
with:
1919
python-version: '3.10'
20+
- name: 'Install uv'
21+
uses: astral-sh/setup-uv@v5
2022
- name: 'Install cookiecutter'
2123
run: pip install cookiecutter
22-
- name: 'Install Poetry'
23-
run: curl -sSL https://install.python-poetry.org | python3 - --version 1.3.2
2424
- name: 'Generate template'
2525
run: cookiecutter --no-input . project_name='Test Project'
2626
- name: 'Generate lock file'
27-
run: poetry -C ./test-project lock
27+
run: uv --project ./test-project lock
2828
- name: 'Cache generated project'
29-
uses: actions/cache@v3
29+
uses: actions/cache@v4
3030
with:
3131
path: ./test-project
3232
key: test-project-${{ github.run_id }}
@@ -37,16 +37,16 @@ jobs:
3737
runs-on: ubuntu-latest
3838
strategy:
3939
matrix:
40-
python-version: ['3.10', '3.11']
40+
python-version: ['3.10', '3.11', '3.12', '3.13']
4141
steps:
4242
- name: 'Install Python'
43-
uses: actions/setup-python@v4
43+
uses: actions/setup-python@v5
4444
with:
4545
python-version: ${{ matrix.python-version }}
46-
- name: 'Install Poetry'
47-
run: curl -sSL https://install.python-poetry.org | python3 - --version 1.3.2
46+
- name: 'Install uv'
47+
uses: astral-sh/setup-uv@v5
4848
- name: 'Restore generated project'
49-
uses: actions/cache@v3
49+
uses: actions/cache@v4
5050
with:
5151
path: ./test-project
5252
key: test-project-${{ github.run_id }}
Lines changed: 39 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
POETRY := poetry
2-
POETRY_RUN := $(POETRY) run
1+
UV := uv
2+
UV_RUN := $(UV) run --
33

44

55
default: check test-unit
@@ -13,11 +13,7 @@ clean:
1313

1414
.PHONY: build
1515
build:
16-
$(POETRY) build
17-
18-
.PHONY: poetry-install
19-
poetry-install:
20-
$(POETRY) install
16+
$(uv) build
2117

2218

2319
# Tests
@@ -26,14 +22,17 @@ TEST_ARGS :=
2622

2723
test: test-all
2824

29-
test-all: poetry-install
30-
$(POETRY_RUN) pytest src/tests --maxfail=1 --verbose --durations=0 --numprocesses=4 --dist=worksteal $(TEST_ARGS)
25+
.PHONY: test-all
26+
test-all:
27+
$(UV_RUN) pytest src/tests --maxfail=1 --verbose --durations=0 --numprocesses=4 --dist=worksteal $(TEST_ARGS)
3128

32-
test-unit: poetry-install
33-
$(POETRY_RUN) pytest src/tests/unit --maxfail=1 --verbose $(TEST_ARGS)
29+
.PHONY: test-unit
30+
test-unit:
31+
$(UV_RUN) pytest src/tests/unit --maxfail=1 --verbose $(TEST_ARGS)
3432

35-
test-integration: poetry-install
36-
$(POETRY_RUN) pytest src/tests/integration --maxfail=1 --verbose --durations=0 --numprocesses=4 --dist=worksteal $(TEST_ARGS)
33+
.PHONY: test-integration
34+
test-integration:
35+
$(UV_RUN) pytest src/tests/integration --maxfail=1 --verbose --durations=0 --numprocesses=4 --dist=worksteal $(TEST_ARGS)
3736

3837

3938
# Coverage
@@ -59,34 +58,43 @@ cov-integration: test-integration
5958
format: autoflake isort black
6059
check: check-flake8 check-mypy check-autoflake check-isort check-black
6160

62-
check-flake8: poetry-install
63-
$(POETRY_RUN) flake8 src
61+
.PHONY: check-flake8
62+
check-flake8:
63+
$(UV_RUN) flake8 src
6464

65-
check-mypy: poetry-install
66-
$(POETRY_RUN) mypy src
65+
.PHONY: check-mypy
66+
check-mypy:
67+
$(UV_RUN) mypy src
6768

68-
autoflake: poetry-install
69-
$(POETRY_RUN) autoflake --quiet --in-place src
69+
.PHONY: autoflake
70+
autoflake:
71+
$(UV_RUN) autoflake --quiet --in-place src
7072

71-
check-autoflake: poetry-install
72-
$(POETRY_RUN) autoflake --quiet --check src
73+
.PHONY: check-autoflake
74+
check-autoflake:
75+
$(UV_RUN) autoflake --quiet --check src
7376

74-
isort: poetry-install
75-
$(POETRY_RUN) isort src
77+
.PHONY: isort
78+
isort:
79+
$(UV_RUN) isort src
7680

77-
check-isort: poetry-install
78-
$(POETRY_RUN) isort --check src
81+
.PHONY: check-isort
82+
check-isort:
83+
$(UV_RUN) isort --check src
7984

80-
black: poetry-install
81-
$(POETRY_RUN) black src
85+
.PHONY: black
86+
black:
87+
$(UV_RUN) black src
8288

83-
check-black: poetry-install
84-
$(POETRY_RUN) black --check src
89+
.PHONY: check-black
90+
check-black:
91+
$(UV_RUN) black --check src
8592

8693

8794
# Optional tools
8895

8996
SRC_FILES := $(shell find src -type f -name '*.py')
9097

91-
pyupgrade: poetry-install
92-
$(POETRY_RUN) pyupgrade --py310-plus $(SRC_FILES)
98+
.PHONY: pyupgrade
99+
pyupgrade:
100+
$(UV_RUN) pyupgrade --py310-plus $(SRC_FILES)

{{cookiecutter.project_slug}}/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
## Installation
55

6-
Prerequsites: `python >= 3.10`, `pip >= 20.0.2`, `poetry >= 1.3.2`.
6+
Prerequsites: `python >= 3.10`, [`uv`](https://docs.astral.sh/uv/).
77

88
```bash
99
make build
@@ -19,5 +19,5 @@ Use `make` to run common tasks (see the [Makefile](Makefile) for a complete list
1919
* `make check`: Check code style
2020
* `make format`: Format code
2121
* `make test-unit`: Run unit tests
22+
* `make test-integration`: Run integration tests
2223

23-
For interactive use, spawn a shell with `poetry shell` (after `poetry install`), then run an interpreter.
Lines changed: 60 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,74 @@
11
{
22
description = "{{ cookiecutter.project_slug }} - {{ cookiecutter.description }}";
33
inputs = {
4-
nixpkgs.url = "nixpkgs/nixos-22.05";
4+
nixpkgs.url = "nixpkgs/nixos-25.05";
55
flake-utils.url = "github:numtide/flake-utils";
6-
poetry2nix.url = "github:nix-community/poetry2nix";
6+
uv2nix.url = "github:pyproject-nix/uv2nix/680e2f8e637bc79b84268949d2f2b2f5e5f1d81c";
7+
# stale nixpkgs is missing the alias `lib.match` -> `builtins.match`
8+
# therefore point uv2nix to a patched nixpkgs, which introduces this alias
9+
# this is a temporary solution until nixpkgs us up-to-date again
10+
uv2nix.inputs.nixpkgs.url = "github:runtimeverification/nixpkgs/libmatch";
11+
# inputs.nixpkgs.follows = "nixpkgs";
12+
pyproject-build-systems.url = "github:pyproject-nix/build-system-pkgs/7dba6dbc73120e15b558754c26024f6c93015dd7";
13+
pyproject-build-systems = {
14+
inputs.nixpkgs.follows = "uv2nix/nixpkgs";
15+
inputs.uv2nix.follows = "uv2nix";
16+
inputs.pyproject-nix.follows = "uv2nix/pyproject-nix";
17+
};
18+
pyproject-nix.follows = "uv2nix/pyproject-nix";
719
};
8-
outputs = { self, nixpkgs, flake-utils, poetry2nix }:
9-
let
10-
allOverlays = [
11-
poetry2nix.overlay
12-
(final: prev: {
13-
{{ cookiecutter.project_slug }} = prev.poetry2nix.mkPoetryApplication {
14-
python = prev.python310;
15-
projectDir = ./.;
16-
groups = [];
17-
# We remove `dev` from `checkGroups`, so that poetry2nix does not try to resolve dev dependencies.
18-
checkGroups = [];
19-
};
20-
})
21-
];
22-
in flake-utils.lib.eachSystem [
20+
outputs = { self, nixpkgs, flake-utils, pyproject-nix, pyproject-build-systems, uv2nix }:
21+
let
22+
pythonVer = "310";
23+
in flake-utils.lib.eachSystem [
2324
"x86_64-linux"
2425
"x86_64-darwin"
2526
"aarch64-linux"
2627
"aarch64-darwin"
2728
] (system:
28-
let
29-
pkgs = import nixpkgs {
30-
inherit system;
31-
overlays = allOverlays;
29+
let
30+
# due to the nixpkgs that we use in this flake being outdated, uv is also heavily outdated
31+
# we can instead use the binary release of uv provided by uv2nix for now
32+
uvOverlay = final: prev: {
33+
uv = uv2nix.packages.${final.system}.uv-bin;
34+
};
35+
{{ cookiecutter.project_slug }}Overlay = final: prev: {
36+
{{ cookiecutter.project_slug }} = final.callPackage ./nix/{{ cookiecutter.project_slug }} {
37+
inherit pyproject-nix pyproject-build-systems uv2nix;
38+
python = final."python${pythonVer}";
3239
};
33-
in {
34-
packages = rec {
35-
inherit (pkgs) {{ cookiecutter.project_slug }};
36-
default = {{ cookiecutter.project_slug }};
40+
};
41+
pkgs = import nixpkgs {
42+
inherit system;
43+
overlays = [
44+
uvOverlay
45+
{{ cookiecutter.project_slug }}Overlay
46+
];
47+
};
48+
python = pkgs."python${pythonVer}";
49+
in {
50+
devShells.default = pkgs.mkShell {
51+
name = "uv develop shell";
52+
buildInputs = [
53+
python
54+
pkgs.uv
55+
];
56+
env = {
57+
# prevent uv from managing Python downloads and force use of specific
58+
UV_PYTHON_DOWNLOADS = "never";
59+
UV_PYTHON = python.interpreter;
3760
};
38-
}) // {
39-
overlay = nixpkgs.lib.composeManyExtensions allOverlays;
61+
shellHook = ''
62+
unset PYTHONPATH
63+
'';
64+
};
65+
packages = rec {
66+
inherit (pkgs) {{ cookiecutter.project_slug }};
67+
default = {{ cookiecutter.project_slug }};
68+
};
69+
}) // {
70+
overlays.default = final: prev: {
71+
inherit (self.packages.${final.system}) {{ cookiecutter.project_slug }};
4072
};
73+
};
4174
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
## Notes
2+
#### git submodules
3+
If you use git submodules that are not required for building the project with `nix`, then you should add these directories to the `gitignoreSourcePure` list in `nix/{{ cookiecutter.project_slug }}/default.nix`, see the respective left-over `TODO` in the nix file. Otherwise, the nix build that is uploaded to the nix binary cache by CI might not match the version that is requested by `kup`, in case this project is offered as a package by `kup`. This is due to weird behaviour in regards to git submodules by `git` and `nix`, where a submodule directory might exist and be empty or instead not exist, which impacts the resulting nix hash, which is of impartance when offering/downloading cached nix derivations. See [runtimeverification/k/pull/4804](https://github.com/runtimeverification/k/pull/4804) for more information.
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
final: prev:
2+
let
3+
inherit (final) resolveBuildSystem;
4+
inherit (builtins) mapAttrs;
5+
6+
# Build system dependencies specified in the shape expected by resolveBuildSystem
7+
# The empty lists below are lists of optional dependencies.
8+
#
9+
# A package `foo` with specification written as:
10+
# `setuptools-scm[toml]` in pyproject.toml would be written as
11+
# `foo.setuptools-scm = [ "toml" ]` in Nix
12+
buildSystemOverrides = {
13+
# add dependencies here, e.g.:
14+
# pyperclip.setuptools = [ ];
15+
};
16+
in
17+
mapAttrs (
18+
name: spec:
19+
prev.${name}.overrideAttrs (old: {
20+
nativeBuildInputs = old.nativeBuildInputs ++ resolveBuildSystem spec;
21+
})
22+
) buildSystemOverrides
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
{
2+
lib,
3+
callPackage,
4+
nix-gitignore,
5+
6+
pyproject-nix,
7+
pyproject-build-systems,
8+
uv2nix,
9+
10+
python
11+
}:
12+
let
13+
pyproject-util = callPackage pyproject-nix.build.util {};
14+
pyproject-packages = callPackage pyproject-nix.build.packages {
15+
inherit python;
16+
};
17+
18+
# load a uv workspace from a workspace root
19+
workspace = uv2nix.lib.workspace.loadWorkspace {
20+
workspaceRoot = lib.cleanSource (nix-gitignore.gitignoreSourcePure [
21+
../../.gitignore
22+
".github/"
23+
"result*"
24+
# do not include submodule directories that might be initilized empty or non-existent due to nix/git
25+
# otherwise cachix build might not match the version that is requested by `kup`
26+
# TODO: for new projects, add your submodule directories that are not required for nix builds here!
27+
# e.g., `"/docs/external-computation"` with `external-computation` being a git submodule directory
28+
# "/docs/external-computation"
29+
] ../..
30+
);
31+
};
32+
33+
# create overlay
34+
lockFileOverlay = workspace.mkPyprojectOverlay {
35+
# prefer "wheel" over "sdist" due to maintance overhead
36+
# there is no bundled set of overlays for "sdist" in uv2nix, in contrast to poetry2nix
37+
sourcePreference = "wheel";
38+
};
39+
40+
buildSystemsOverlay = import ./build-systems-overlay.nix;
41+
42+
# construct package set
43+
pythonSet = pyproject-packages.overrideScope (lib.composeManyExtensions [
44+
# make build tools available by default as these are not necessarily specified in python lock files
45+
pyproject-build-systems.overlays.default
46+
# include all packages from the python lock file
47+
lockFileOverlay
48+
# add build system overrides to certain python packages
49+
buildSystemsOverlay
50+
]);
51+
in pyproject-util.mkApplication {
52+
# default dependancy group enables no optional dependencies and no dependency-groups
53+
venv = pythonSet.mkVirtualEnv "{{ cookiecutter.project_slug }}-env" workspace.deps.default;
54+
package = pythonSet.{{ cookiecutter.project_slug }};
55+
}

0 commit comments

Comments
 (0)