Skip to content
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
a9a4757
introducing lock files for testing
jamesp Apr 27, 2021
642a03e
Testing github refresh workflow
jamesp Apr 27, 2021
bb444a8
Update refresh-lockfiles.yml
jamesp Apr 27, 2021
bd58e70
Update refresh-lockfiles.yml
jamesp Apr 27, 2021
f6c2ca5
Action making lockfiles and setting as artifacts
jamesp Apr 27, 2021
596d2a0
Update refresh-lockfiles.yml
jamesp Apr 27, 2021
9dea68d
Update refresh-lockfiles.yml
jamesp Apr 27, 2021
81ba3aa
Update refresh-lockfiles.yml
jamesp Apr 27, 2021
26b3e35
Update refresh-lockfiles.yml
jamesp Apr 27, 2021
5552016
Update refresh-lockfiles.yml
jamesp Apr 27, 2021
ddb3a8e
Added PR creation
jamesp Apr 27, 2021
b20615c
Cleanup refresh-lockfiles.yml
jamesp Apr 27, 2021
8f71bc1
Update .cirrus.yml
jamesp Apr 27, 2021
fc07f17
Update refresh-lockfiles.yml
jamesp Apr 27, 2021
22e325d
Back to cartopy 0.18
jamesp Apr 28, 2021
2ce13b2
Merge branch 'ci-lock' of github.com:jamesp/iris into ci-lock
jamesp Apr 28, 2021
153254d
Script for updating lockfiles
jamesp Apr 28, 2021
59c93b1
License header on update_lockfiles script
jamesp Apr 28, 2021
45311ee
testing reupload_on_changes key in nox cache
jamesp Apr 28, 2021
35570d0
Documentation update
jamesp Apr 28, 2021
0305416
Added whats new entry
jamesp Apr 28, 2021
39c3b6b
Fixed a horrendous number of typos
jamesp Apr 29, 2021
90a0599
Type annotations in the noxfile completed
jamesp Apr 29, 2021
8480c21
switched os.path -> pathlib in tools/update_lockfiles.py
jamesp Apr 29, 2021
48e39b5
Apply suggestions from @trexfeathers code review
jamesp Apr 29, 2021
a539b25
Updated imagehashes
jamesp May 4, 2021
5b3e876
Updated documentation of the github workflow
jamesp May 4, 2021
e3af577
Merge branch 'ci-lock' of github.com:jamesp/iris into ci-lock
jamesp May 4, 2021
3301584
Updated image hash for anomaly log plot
jamesp May 4, 2021
5241637
Reverting imagerepo.json to state at 48e39b57
jamesp May 4, 2021
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
5 changes: 2 additions & 3 deletions .cirrus.yml
Original file line number Diff line number Diff line change
Expand Up @@ -79,11 +79,10 @@ linux_task_template: &LINUX_TASK_TEMPLATE
- echo "$(date +%Y).$(expr $(date +%U) / ${CACHE_PERIOD}):${CARTOPY_CACHE_BUILD}"
nox_cache:
folder: ${CIRRUS_WORKING_DIR}/.nox
reupload_on_changes: true
fingerprint_script:
- echo "${CIRRUS_TASK_NAME}"
- echo "$(date +%Y).$(expr $(date +%U) / ${CACHE_PERIOD}):${NOX_CACHE_BUILD}"
- sha256sum ${CIRRUS_WORKING_DIR}/requirements/ci/py$(echo ${PY_VER} | tr -d ".").yml

- echo "${NOX_CACHE_BUILD}"

#
# YAML alias for compute credits.
Expand Down
73 changes: 73 additions & 0 deletions .github/workflows/refresh-lockfiles.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# This workflow periodically creates new environment lock files based on the newest
# available dependency pacakges on conda-forge.
Comment thread
rcomer marked this conversation as resolved.
Outdated
#
# For environments that have changed, a pull request will be made and submitted
# to the master branch

name: Refresh Lockfiles


on:
workflow_dispatch:
schedule:
# Run once a week on a Saturday night
- cron: 1 0 * * 6


jobs:

gen_lockfiles:
# this is a matrix job: it splits to create new lockfiles for each
# of the CI test python versions.
# this list below should be changed when covering more python versions
# TODO: generate this matrix automatically from the list of available py**.yml files
# ref: https://tomasvotruba.com/blog/2020/11/16/how-to-make-dynamic-matrix-in-github-actions/
runs-on: ubuntu-latest

strategy:
matrix:
python: ['36', '37', '38']

steps:
- uses: actions/checkout@v2
- name: install conda-lock
run: |
source $CONDA/bin/activate base
conda install -y -c conda-forge conda-lock
- name: generate lockfile
run: |
$CONDA/bin/conda-lock lock -p linux-64 -f requirements/ci/py${{matrix.python}}.yml
mv conda-linux-64.lock py${{matrix.python}}-linux-64.lock
- name: output lockfile
uses: actions/upload-artifact@v2
with:
path: py${{matrix.python}}-linux-64.lock

create_pr:
# once the matrix job has completed all the lock files will have been uploaded as artifacts.
# Download the artifacts, add them to the repo, and create a PR.
runs-on: ubuntu-latest
needs: gen_lockfiles

steps:
- uses: actions/checkout@v2
- name: get artifacts
uses: actions/download-artifact@v2
with:
path: artifacts

- name: Update lock files in repo
run: |
cp artifacts/artifact/*.lock requirements/ci/nox.lock
rm -r artifacts

- name: Create Pull Request
uses: peter-evans/create-pull-request@052fc72b4198ba9fbc81b818c6e1859f747d49a8
with:
commit-message: Updated environment lockfiles
delete-branch: true
branch: lockfiles
branch-suffix: timestamp
title: Update CI environment lockfiles
body: |
Lockfiles updated to the latest resolvable environment.
2 changes: 2 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
lockfiles:
python tools/update_lockfiles.py -o requirements/ci/nox.lock requirements/ci/py*.yml
30 changes: 30 additions & 0 deletions docs/src/developers_guide/contributing_ci_tests.rst
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,36 @@ The above `cirrus-ci`_ tasks are run automatically against all `Iris`_ branches
on GitHub whenever a pull-request is submitted, updated or merged. See the
`Cirrus-CI Dashboard`_ for details of recent past and active Iris jobs.


.. _cirrus_test_env:

Cirrus CI Test environment
--------------------------

The test environment on the Cirrus-CI service is determined from the requirement files
in `requirements/ci/py**.yml`. These are conda envinroment files that list the entire
Comment thread
rcomer marked this conversation as resolved.
Outdated
set of build, test and run requirements for iris.
Comment thread
trexfeathers marked this conversation as resolved.
Outdated

For reproducible test results, these environments are resolved for all their dependencies
and stored as lock files in `requirements/ci/nox.lock`. The test environments will not
Comment thread
trexfeathers marked this conversation as resolved.
Outdated
resolve the dependencies each time, instead they will use the lock file to reproduce the
same exact environment each time.

**If you have updated the requirement yaml files with new dependencies, you will need to
generate new lock files.** To do this, run the command::

python tools/update_lockfiles.py -o requirements/ci/nox.lock requirements/ci/py*.yml

or simply::

make lockfiles

and add the changed lockfiles to your pull request.

New lockfiles are generated automatically each week to ensure that iris continues to be
Comment thread
trexfeathers marked this conversation as resolved.
Outdated
tested against the latest available version of its dependencies.


.. _skipping Cirrus-CI tasks:

Skipping Cirrus-CI Tasks
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ is merged. Before submitting a pull request please consider this list.
#. **Check all modified and new source files conform to the required**
:ref:`code_formatting`.

#. **Check all new dependencies added to the requirements/ci/*.yml files.** If
Comment thread
trexfeathers marked this conversation as resolved.
Outdated
dependencies have been added new nox testing lockfieles should be generated too,
Comment thread
rcomer marked this conversation as resolved.
Outdated
see :ref:`cirrus_test_env`.

#. **Check the source documentation been updated to explain all new or changed
features**. See :ref:`docstrings`.

Expand Down
3 changes: 3 additions & 0 deletions docs/src/whatsnew/latest.rst
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,8 @@ This document explains the changes made to Iris for this release
#. `@bjlittle`_ updated the perceptual imagehash graphical test support for
`matplotlib`_ 3.4.1. (:pull:`4087`)

#. `@jamesp`_ switched `cirrus-ci`_ testing and `nox`_
testing to use `conda-lock`_ files for static test environments. (:pull:`4108`)

.. comment
Whatsnew author names (@github name) in alphabetical order. Note that,
Expand All @@ -175,3 +177,4 @@ This document explains the changes made to Iris for this release
.. _Python 3.8: https://www.python.org/downloads/release/python-380/
.. _README.md: https://github.com/SciTools/iris#-----
.. _xxhash: http://cyan4973.github.io/xxHash/
.. _conda-lock: https://github.com/conda-incubator/conda-lock
109 changes: 64 additions & 45 deletions noxfile.py
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from pathlib import Path

import nox
from nox.logger import logger


#: Default to reusing any pre-existing nox environments.
Expand All @@ -25,57 +26,64 @@
CARTOPY_CACHE_DIR = os.environ.get("HOME") / Path(".local/share/cartopy")


def venv_cached(session):
"""
Determine whether the nox session environment has been cached.

Parameters
----------
session: object
A `nox.sessions.Session` object.
def session_lockfile(session: nox.sessions.Session) -> Path:
"""Return the path of the session lockfile."""
return Path(
f"requirements/ci/nox.lock/py{session.python.replace('.', '')}-linux-64.lock"
)
Comment thread
trexfeathers marked this conversation as resolved.

Returns
-------
bool
Whether the session has been cached.

"""
result = False
yml = Path(f"requirements/ci/py{session.python.replace('.', '')}.yml")
def session_cachefile(session: nox.sessions.Session) -> Path:
"""Returns the path of the session lockfile cache."""
lockfile = session_lockfile(session)
tmp_dir = Path(session.create_tmp())
cache = tmp_dir / yml.name
cache = tmp_dir / lockfile.name
return cache


def venv_populated(session: nox.sessions.Session) -> bool:
"""Returns True if the conda venv has been created
and the list of packages in the lockfile installed."""
return session_cachefile(session).is_file()


def venv_changed(session: nox.sessions.Session) -> bool:
"""Returns True if the installed session is different to that specified
in the lockfile."""
cache = session_cachefile(session)
lockfile = session_lockfile(session)
if cache.is_file():
with open(yml, "rb") as fi:
with open(lockfile, "rb") as fi:
expected = hashlib.sha256(fi.read()).hexdigest()
with open(cache, "r") as fi:
actual = fi.read()
result = actual == expected
return result
return actual != expected
else:
return False


def cache_venv(session):
def cache_venv(session: nox.sessions.Session):
"""
Cache the nox session environment.

This consists of saving a hexdigest (sha256) of the associated
conda requirements YAML file.
conda lock file.

Parameters
----------
session: object
A `nox.sessions.Session` object.

"""
yml = Path(f"requirements/ci/py{session.python.replace('.', '')}.yml")
with open(yml, "rb") as fi:
lockfile = session_lockfile(session)
cache = session_cachefile(session)
with open(lockfile, "rb") as fi:
hexdigest = hashlib.sha256(fi.read()).hexdigest()
tmp_dir = Path(session.create_tmp())
cache = tmp_dir / yml.name
with open(cache, "w") as fo:
fo.write(hexdigest)


def cache_cartopy(session):
def cache_cartopy(session: nox.sessions.Session):
"""
Determine whether to cache the cartopy natural earth shapefiles.

Expand Down Expand Up @@ -112,41 +120,47 @@ def prepare_venv(session):
- https://github.com/theacodes/nox/issues/260

"""
if not venv_cached(session):
# Determine the conda requirements yaml file.
fname = f"requirements/ci/py{session.python.replace('.', '')}.yml"
# Back-door approach to force nox to use "conda env update".
command = (
"conda",
"env",
"update",
f"--prefix={session.virtualenv.location}",
f"--file={fname}",
"--prune",
)
session._run(*command, silent=True, external="error")
lockfile = session_lockfile(session)
venv_dir = session.virtualenv.location_name

if not venv_populated(session):
# environment has been created but pacakages not yet installed
Comment thread
rcomer marked this conversation as resolved.
Outdated
# populate the environment from the lockfile
logger.debug(f"Populating conda env at {venv_dir}")
session.conda_install("--file", str(lockfile))
cache_venv(session)

elif venv_changed(session):
# destroy the environment and rebuild it
logger.debug(f"Lockfile changed. Re-creating conda env at {venv_dir}")
_re_orig = session.virtualenv.reuse_existing
session.virtualenv.reuse_existing = False
session.virtualenv.create()
session.conda_install("--file", str(lockfile))
session.virtualenv.reuse_existing = _re_orig
cache_venv(session)

logger.debug(f"Environment {venv_dir} up to date")

cache_cartopy(session)
session.install("--no-deps", "--editable", ".")

# Determine whether verbose diagnostics have been requested
# from the command line.
verbose = "-v" in session.posargs or "--verbose" in session.posargs

if verbose:
session.run("conda", "info")
session.run("conda", "list", f"--prefix={session.virtualenv.location}")
session.run("conda", "list", f"--prefix={venv_dir}")
session.run(
"conda",
"list",
f"--prefix={session.virtualenv.location}",
f"--prefix={venv_dir}",
"--explicit",
)


@nox.session
def flake8(session):
def flake8(session: nox.sessions.Session):
"""
Perform flake8 linting of iris.

Expand All @@ -165,7 +179,7 @@ def flake8(session):


@nox.session
def black(session):
def black(session: nox.sessions.Session):
"""
Perform black format checking of iris.

Expand All @@ -184,7 +198,7 @@ def black(session):


@nox.session(python=PY_VER, venv_backend="conda")
def tests(session):
def tests(session: nox.sessions.Session):
Comment thread
trexfeathers marked this conversation as resolved.
"""
Perform iris system, integration and unit tests.

Expand All @@ -195,6 +209,8 @@ def tests(session):

"""
prepare_venv(session)
session.install("--no-deps", "--editable", ".")

session.run(
"python",
"-m",
Expand All @@ -216,6 +232,7 @@ def gallery(session):

"""
prepare_venv(session)
session.install("--no-deps", "--editable", ".")
session.run(
"python",
"-m",
Expand All @@ -236,6 +253,7 @@ def doctest(session):

"""
prepare_venv(session)
session.install("--no-deps", "--editable", ".")
session.cd("docs")
session.run(
"make",
Expand All @@ -262,6 +280,7 @@ def linkcheck(session):

"""
prepare_venv(session)
session.install("--no-deps", "--editable", ".")
session.cd("docs")
session.run(
"make",
Expand Down
Loading