Skip to content

Commit 04cc344

Browse files
authored
CI: Utilize uv lockfile for reproducible test environments (#6640)
This PR removes the requirement files in `requirements/` folder and replaces them with a single universal lockfile `uv.lock`. "universal" in this case means that it contains resolved package versions for all supported python versions and operating systems. More on the uv lockfiles can be found in their docs: https://docs.astral.sh/uv/concepts/projects/layout/#the-lockfile When modifying the dependencies in `pyproject.toml`, the lockfile needs to be updated by running ``` uv lock ``` (Note that using e.g. `uv add`, `uv remove` or `uv sync` updates the lockfile automatically) This relative simplicity compared to the previous custom solution allows for much easier development and I could delete a lot of custom YAML in CI. To check whether the lockfile is up to date, one can simply run ``` uv lock --locked ``` This command is run automatically as a pre-commit hook whenever `pyproject.toml` is changed. >[!NOTE] > Developers don't necessarily need to use the lockfile locally, nor are they required to have `uv` installed, **unless** they need to modify dependencies in `pyproject.toml`
1 parent ec52f4e commit 04cc344

15 files changed

+4924
-1199
lines changed

.github/CODEOWNERS

+4-6
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,7 @@
22
# currently active dependency manager (DM) to trigger an automatic review
33
# request from the DM upon changes. Please see AEP-002 for details:
44
# https://github.com/aiidateam/AEP/tree/master/002_dependency_management
5-
setup.* @aiidateam/dependency-manager
6-
environment.yml @aiidateam/dependency-manager
7-
requirements*.txt @aiidateam/dependency-manager
8-
pyproject.toml @aiidateam/dependency-manager
9-
utils/dependency_management.py @aiidateam/dependency-manager
10-
.github/workflows/dm.yml @aiidateam/dependency-manager
5+
environment.yml @unkcpz @agoscinski
6+
pyproject.toml @unkcpz @agoscinski
7+
uv.lock @unkcpz @agoscinski
8+
utils/dependency_management.py @unkcpz @agoscinski

.github/actions/install-aiida-core/action.yml

+17-20
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,11 @@ inputs:
1212
required: false
1313
# NOTE: Hard-learned lesson: we cannot use type=boolean here, apparently :-(
1414
# https://stackoverflow.com/a/76294014
15-
from-requirements:
16-
description: Install aiida-core dependencies from pre-compiled requirements.txt file
15+
# NOTE2: When installing from lockfile, aiida-core and its dependencies
16+
# are installed in a virtual environment located in .venv directory.
17+
# Subsuquent jobs steps must either activate the environment or use `uv run`
18+
from-lock:
19+
description: Install aiida-core dependencies from a uv lock file
1720
default: 'true'
1821
required: false
1922

@@ -25,26 +28,20 @@ runs:
2528
with:
2629
python-version: ${{ inputs.python-version }}
2730

28-
- name: Install uv installer
29-
run: curl --proto '=https' --tlsv1.2 -LsSf https://${{ env.UV_URL }} | sh
30-
env:
31-
UV_VERSION: 0.2.9
32-
UV_URL: github.com/astral-sh/uv/releases/download/$UV_VERSION/uv-installer.sh
33-
shell: bash
31+
- name: Set up uv
32+
uses: astral-sh/setup-uv@v4
33+
with:
34+
version: 0.5.6
3435

35-
- name: Install dependencies from requirements-py-*.txt
36-
if: ${{ inputs.from-requirements == 'true' }}
37-
run: uv pip install --system -r requirements/requirements-py-${{ inputs.python-version }}.txt
36+
- name: Install dependencies from uv lock
37+
if: ${{ inputs.from-lock == 'true' }}
38+
# NOTE: We're asserting that the lockfile is up to date
39+
# NOTE2: 'pre-commit' extra recursively contains other extras
40+
# needed to run the tests.
41+
run: uv sync --locked --extra pre-commit
3842
shell: bash
3943

4044
- name: Install aiida-core
41-
run: uv pip install --system ${{ env.NO_DEPS }} -e .${{ inputs.extras }}
42-
env:
43-
# Don't install dependencies if they were installed through requirements file AND
44-
# if no extras are required.
45-
#
46-
# If this syntax looks weird to you, dear reader, know that this is
47-
# GHA's way to do ternary operator. :-/
48-
# https://docs.github.com/en/actions/learn-github-actions/expressions#example
49-
NO_DEPS: ${{ (inputs.from-requirements == 'true' && inputs.extras == '') && '--no-deps' || '' }}
45+
if: ${{ inputs.from-lock != 'true' }}
46+
run: uv pip install --system -e .${{ inputs.extras }}
5047
shell: bash

.github/workflows/ci-code.yml

+5-38
Original file line numberDiff line numberDiff line change
@@ -18,40 +18,8 @@ env:
1818

1919
jobs:
2020

21-
check-requirements:
22-
23-
runs-on: ubuntu-latest
24-
timeout-minutes: 5
25-
26-
steps:
27-
- uses: actions/checkout@v4
28-
29-
- name: Set up Python 3.12
30-
uses: actions/setup-python@v5
31-
with:
32-
python-version: '3.12'
33-
34-
- name: Install utils/ dependencies
35-
run: pip install -r utils/requirements.txt
36-
37-
- name: Check requirements files
38-
id: check_reqs
39-
run: python ./utils/dependency_management.py check-requirements DEFAULT
40-
41-
- name: Create commit comment
42-
if: failure() && steps.check_reqs.outputs.error
43-
uses: peter-evans/commit-comment@v3
44-
with:
45-
path: pyproject.toml
46-
body: |
47-
${{ steps.check_reqs.outputs.error }}
48-
49-
Click [here](https://github.com/aiidateam/aiida-core/wiki/AiiDA-Dependency-Management) for more information on dependency management.
50-
5121
tests:
5222

53-
needs: [check-requirements]
54-
5523
runs-on: ubuntu-latest
5624
timeout-minutes: 45
5725

@@ -96,15 +64,16 @@ jobs:
9664
python-version: ${{ matrix.python-version }}
9765

9866
- name: Setup environment
99-
run: .github/workflows/setup.sh
67+
# Note: The virtual environment in .venv was created by uv in previous step
68+
run: source .venv/bin/activate && .github/workflows/setup.sh
10069

10170
- name: Run test suite
10271
env:
10372
AIIDA_TEST_PROFILE: test_aiida
10473
AIIDA_WARN_v3: 1
10574
# Python 3.12 has a performance regression when running with code coverage
10675
# so run code coverage only for python 3.9.
107-
run: pytest -n auto --db-backend psql -m 'not nightly' tests/ ${{ matrix.python-version == '3.9' && '--cov aiida' || '' }}
76+
run: uv run pytest -n auto --db-backend psql -m 'not nightly' tests/ ${{ matrix.python-version == '3.9' && '--cov aiida' || '' }}
10877

10978
- name: Upload coverage report
11079
if: matrix.python-version == 3.9 && github.repository == 'aiidateam/aiida-core'
@@ -118,7 +87,6 @@ jobs:
11887

11988
tests-presto:
12089

121-
needs: [check-requirements]
12290
runs-on: ubuntu-latest
12391
timeout-minutes: 20
12492

@@ -139,12 +107,11 @@ jobs:
139107
- name: Run test suite
140108
env:
141109
AIIDA_WARN_v3: 0
142-
run: pytest -n auto -m 'presto' tests/
110+
run: uv run pytest -n auto -m 'presto' tests/
143111

144112

145113
verdi:
146114

147-
needs: [check-requirements]
148115
runs-on: ubuntu-latest
149116
timeout-minutes: 10
150117

@@ -155,7 +122,7 @@ jobs:
155122
uses: ./.github/actions/install-aiida-core
156123
with:
157124
python-version: '3.12'
158-
from-requirements: 'false'
125+
from-lock: 'false'
159126

160127
- name: Run verdi tests
161128
run: |

.github/workflows/docs-build.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ jobs:
2525
with:
2626
python-version: '3.9'
2727
extras: '[docs,tests,rest,atomic_tools]'
28-
from-requirements: 'false'
28+
from-lock: 'false'
2929

3030
- name: Build HTML docs
3131
id: linkcheck

.github/workflows/release.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ jobs:
4242
with:
4343
python-version: '3.11'
4444
extras: '[pre-commit]'
45-
from-requirements: 'false'
45+
from-lock: 'false'
4646

4747
- name: Run pre-commit
4848
run: pre-commit run --all-files || ( git status --short ; git diff ; exit 1 )

.github/workflows/test-install.yml

+13-143
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ on:
44
pull_request:
55
paths:
66
- environment.yml
7-
- '**/requirements*.txt'
87
- pyproject.toml
98
- util/dependency_management.py
109
- .github/workflows/test-install.yml
@@ -14,7 +13,7 @@ on:
1413

1514
# https://docs.github.com/en/actions/using-jobs/using-concurrency
1615
concurrency:
17-
# only cancel in-progress jobs or runs for the current workflow - matches against branch & tags
16+
# only cancel in-progress jobs or runs for the current workflow - matches against branch & tags
1817
group: ${{ github.workflow }}-${{ github.ref }}
1918
cancel-in-progress: true
2019

@@ -30,44 +29,24 @@ jobs:
3029
steps:
3130
- uses: actions/checkout@v4
3231

33-
- name: Set up Python 3.9
32+
- name: Set up Python 3.11
3433
uses: actions/setup-python@v5
3534
with:
3635
python-version: '3.9'
3736

38-
- name: Install utils/ dependencies
39-
run: pip install -r utils/requirements.txt
40-
41-
- name: Validate
42-
run: |
43-
python ./utils/dependency_management.py check-requirements
44-
python ./utils/dependency_management.py validate-all
45-
46-
resolve-pip-dependencies:
47-
# Check whether the environments defined in the requirements/* files are
48-
# resolvable.
49-
50-
needs: [validate-dependency-specification]
51-
if: github.repository == 'aiidateam/aiida-core'
52-
runs-on: ubuntu-latest
53-
timeout-minutes: 5
54-
55-
strategy:
56-
fail-fast: false
57-
matrix:
58-
python-version: ['3.9', '3.10', '3.11', '3.12']
37+
- name: Set up uv
38+
uses: astral-sh/setup-uv@v4
39+
with:
40+
version: 0.5.6
5941

60-
steps:
61-
- uses: actions/checkout@v4
42+
- name: Install utils/ dependencies
43+
run: uv pip install --system -r utils/requirements.txt
6244

63-
- name: Set up Python ${{ matrix.python-version }}
64-
uses: actions/setup-python@v5
65-
with:
66-
python-version: ${{ matrix.python-version }}
45+
- name: Validate uv lockfile
46+
run: uv lock --locked
6747

68-
- name: Resolve dependencies from requirements file.
69-
run: |
70-
pip install --dry-run --ignore-installed -r requirements/requirements-py-${{ matrix.python-version }}.txt
48+
- name: Validate conda environment file
49+
run: python ./utils/dependency_management.py validate-environment-yml
7150

7251
create-conda-environment:
7352
# Verify that we can create a valid conda environment from the environment.yml file.
@@ -220,7 +199,7 @@ jobs:
220199
with:
221200
python-version: ${{ matrix.python-version }}
222201
extras: '[atomic_tools,docs,notebook,rest,tests,tui]'
223-
from-requirements: 'false'
202+
from-lock: 'false'
224203

225204
- name: Setup AiiDA environment
226205
run: .github/workflows/setup.sh
@@ -230,112 +209,3 @@ jobs:
230209
AIIDA_TEST_PROFILE: test_aiida
231210
AIIDA_WARN_v3: 1
232211
run: pytest -n auto --db-backend psql tests -m 'not nightly' tests/
233-
234-
- name: Freeze test environment
235-
run: pip freeze | sed '1d' | tee requirements-py-${{ matrix.python-version }}.txt
236-
237-
# Add python-version specific requirements/ file to the requirements.txt artifact.
238-
# This artifact can be used in the next step to automatically create a pull request
239-
# updating the requirements (in case they are inconsistent with the pyproject.toml file).
240-
- uses: actions/upload-artifact@v4
241-
with:
242-
name: requirements-py-${{ matrix.python-version }}.txt
243-
path: requirements-py-${{ matrix.python-version }}.txt
244-
245-
# Check whether the requirements/ files are consistent with the dependency specification in the pyproject.toml file.
246-
# If the check fails, warn the user via a comment and try to automatically create a pull request to update the files
247-
# (does not work on pull requests from forks).
248-
249-
check-requirements:
250-
251-
needs: tests
252-
253-
runs-on: ubuntu-latest
254-
timeout-minutes: 5
255-
256-
steps:
257-
- uses: actions/checkout@v4
258-
259-
- name: Set up Python 3.9
260-
uses: actions/setup-python@v5
261-
with:
262-
python-version: 3.9
263-
264-
- name: Install utils/ dependencies
265-
run: pip install -r utils/requirements.txt
266-
267-
- name: Check consistency of requirements/ files
268-
id: check_reqs
269-
continue-on-error: true
270-
run: python ./utils/dependency_management.py check-requirements DEFAULT --no-github-annotate
271-
272-
#
273-
# The following steps are only executed if the consistency check failed.
274-
#
275-
- name: Create commit comment
276-
if: steps.check_reqs.outcome == 'Failure' # only run if requirements/ are inconsistent
277-
uses: peter-evans/commit-comment@v3
278-
with:
279-
token: ${{ secrets.GITHUB_TOKEN }}
280-
path: pyproject.toml
281-
body: |
282-
The requirements/ files are inconsistent!
283-
284-
# Check out the base branch so that we can prepare the pull request.
285-
- name: Checkout base branch
286-
if: steps.check_reqs.outcome == 'Failure' # only run if requirements/ are inconsistent
287-
uses: actions/checkout@v4
288-
with:
289-
ref: ${{ github.head_ref }}
290-
clean: true
291-
292-
- name: Download requirements.txt files
293-
if: steps.check_reqs.outcome == 'Failure' # only run if requirements/ are inconsistent
294-
uses: actions/download-artifact@v4
295-
with:
296-
pattern: requirements-py-*
297-
merge-multiple: true
298-
path: requirements
299-
300-
- name: Commit requirements files
301-
if: steps.check_reqs.outcome == 'Failure' # only run if requirements/ are inconsistent
302-
run: |
303-
git add requirements/*
304-
305-
- name: Create pull request for updated requirements files
306-
if: steps.check_reqs.outcome == 'Failure' # only run if requirements/ are inconsistent
307-
id: create_update_requirements_pr
308-
continue-on-error: true
309-
uses: peter-evans/create-pull-request@v7
310-
with:
311-
branch: update-requirements
312-
commit-message: Automated update of requirements/ files.
313-
title: Update requirements/ files.
314-
body: |
315-
Update requirements files to ensure that they are consistent
316-
with the dependencies specified in the 'pyproject.toml' file.
317-
318-
Please note, that this pull request was likely created to
319-
resolve the inconsistency for a specific dependency, however
320-
other versions that have changed since the last update will
321-
be included as part of this commit as well.
322-
323-
Click [here](https://github.com/aiidateam/aiida-core/wiki/AiiDA-Dependency-Management) for more information.
324-
325-
- name: Create PR comment on success
326-
if: steps.create_update_requirements_pr.outcome == 'Success'
327-
uses: peter-evans/create-or-update-comment@v4
328-
with:
329-
issue-number: ${{ github.event.number }}
330-
body: |
331-
I automatically created a pull request (#${{ steps.create_update_requirements_pr.outputs.pr_number }}) that adapts the
332-
requirements/ files according to the dependencies specified in the 'pyproject.toml' file.
333-
334-
- name: Create PR comment on failure
335-
if: steps.create_update_requirements_pr.outcome == 'Failure'
336-
uses: peter-evans/create-or-update-comment@v4
337-
with:
338-
issue-number: ${{ github.event.number }}
339-
body: |
340-
Please update the requirements/ files to ensure that they
341-
are consistent with the dependencies specified in the 'pyproject.toml' file.

0 commit comments

Comments
 (0)