diff --git a/.cirrus.yml b/.cirrus.yml deleted file mode 100644 index 2f227a6c..00000000 --- a/.cirrus.yml +++ /dev/null @@ -1,151 +0,0 @@ -# Reference: -# - https://cirrus-ci.org/guide/writing-tasks/ -# - https://cirrus-ci.org/guide/linux/ -# - https://cirrus-ci.org/guide/macOS/ -# - https://cirrus-ci.org/guide/windows/ -# - https://hub.docker.com/_/gcc/ -# - https://hub.docker.com/_/python/ - -# -# Global defaults. -# -container: - image: python:3.10 - cpu: 2 - memory: 4G - -env: - # Skip specific tasks by name. Set to a non-empty string to skip. - SKIP_TEST_TASK: "" - SKIP_BENCHMARK_TASK: "" - # Maximum cache period (in weeks) before forcing a new cache upload. - CACHE_PERIOD: "0" - # Increment the build number to force new conda cache upload. - CONDA_CACHE_BUILD: "1" - # Increment the build number to force new nox cache upload. - NOX_CACHE_BUILD: "3" - # Increment the build number to force new pip cache upload. - PIP_CACHE_BUILD: "0" - # Pip package to be installed. - PIP_CACHE_PACKAGES: "pip setuptools wheel nox pyyaml" - # Conda packages to be installed. - CONDA_CACHE_PACKAGES: "nox pip pyyaml" - # Use specific custom iris source feature branch. - IRIS_SOURCE: "github:main" - # Git commit hash for iris test data. - IRIS_TEST_DATA_VERSION: "2.2" - # Base directory for the iris-test-data. - IRIS_TEST_DATA_DIR: ${HOME}/iris-test-data - OVERRIDE_TEST_DATA_REPOSITORY: ${IRIS_TEST_DATA_DIR}/test_data - - -# -# YAML alias for the iris-test-data cache. -# -iris_test_data_template: &IRIS_TEST_DATA_TEMPLATE - data_cache: - folder: ${IRIS_TEST_DATA_DIR} - fingerprint_script: - - echo "iris-test-data v${IRIS_TEST_DATA_VERSION}" - populate_script: - - wget --quiet https://github.com/SciTools/iris-test-data/archive/v${IRIS_TEST_DATA_VERSION}.zip -O iris-test-data.zip - - unzip -q iris-test-data.zip - - mv iris-test-data-${IRIS_TEST_DATA_VERSION} ${IRIS_TEST_DATA_DIR} - - -# -# YAML alias for common linux test infra-structure. -# -LINUX_CONDA_TEMPLATE: &LINUX_CONDA_TEMPLATE - auto_cancellation: true - container: - image: gcc:latest - env: - PATH: ${HOME}/miniconda/bin:${PATH} - conda_cache: - folder: ${HOME}/miniconda - fingerprint_script: - - wget --quiet https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh -O miniconda.sh - - echo "${CIRRUS_OS} $(sha256sum miniconda.sh)" - - echo "${CONDA_CACHE_PACKAGES}" - - echo "$(date +%Y).$(expr $(date +%U) / ${CACHE_PERIOD}):${CONDA_CACHE_BUILD}" - - uname -r - populate_script: - - export CONDA_OVERRIDE_LINUX="$(uname -r | cut -d'+' -f1)" - - bash miniconda.sh -b -p ${HOME}/miniconda - - conda config --set always_yes yes --set changeps1 no - - conda config --set show_channel_urls True - - conda config --add channels conda-forge - - conda update --quiet --name base conda - - conda install --quiet --name base ${CONDA_CACHE_PACKAGES} - - -# -# Testing (Linux) -# -test_task: - only_if: ${SKIP_TEST_TASK} == "" - auto_cancellation: true - matrix: - env: - PY_VER: "3.8" - env: - PY_VER: "3.9" - env: - PY_VER: "3.10" - COVERAGE: "true" - name: "${CIRRUS_OS}: py${PY_VER} tests" - << : *LINUX_CONDA_TEMPLATE - nox_cache: - folder: ${CIRRUS_WORKING_DIR}/.nox - reupload_on_changes: true - fingerprint_script: - - echo "${CIRRUS_TASK_NAME}" - - echo "${NOX_CACHE_BUILD}" - - if [ -n "${IRIS_SOURCE}" ]; then echo "${IRIS_SOURCE}"; fi - << : *IRIS_TEST_DATA_TEMPLATE - test_script: - - export CONDA_OVERRIDE_LINUX="$(uname -r | cut -d'+' -f1)" - - nox --session tests -- --verbose - - -# -# Performance Benchmarking (Linux) -# -benchmark_task: - only_if: ${SKIP_BENCHMARK_TASK} == "" - auto_cancellation: true - env: - PY_VER: "3.10" - name: "${CIRRUS_OS}: performance benchmarking" - # Custom clone behaviour to enable ASV to access the PR base branch (if on a - # PR). - clone_script: | - if [ -z "$CIRRUS_PR" ]; then - git clone --branch=$CIRRUS_BRANCH https://x-access-token:${CIRRUS_REPO_CLONE_TOKEN}@github.com/${CIRRUS_REPO_FULL_NAME}.git $CIRRUS_WORKING_DIR - git reset --hard $CIRRUS_CHANGE_IN_REPO - else - git clone --recursive https://x-access-token:${CIRRUS_REPO_CLONE_TOKEN}@github.com/${CIRRUS_REPO_FULL_NAME}.git $CIRRUS_WORKING_DIR - git fetch origin pull/${CIRRUS_PR}/head:pull/${CIRRUS_PR} - git reset --hard $CIRRUS_CHANGE_IN_REPO - fi - << : *LINUX_CONDA_TEMPLATE - asv_cache: - folder: ${CIRRUS_WORKING_DIR}/benchmarks/.asv/env - reupload_on_changes: true - fingerprint_script: - - echo "${CIRRUS_TASK_NAME}" - - if [ -n "${IRIS_SOURCE}" ]; then echo "${IRIS_SOURCE}"; fi - nox_cache: - folder: ${CIRRUS_WORKING_DIR}/.nox - reupload_on_changes: true - fingerprint_script: - - echo "${CIRRUS_TASK_NAME}" - - echo "${NOX_CACHE_BUILD}" - - if [ -n "${IRIS_SOURCE}" ]; then echo "${IRIS_SOURCE}"; fi - benchmarks_script: - - if [ -z "$CIRRUS_BASE_SHA" ]; then export COMPARE="HEAD~"; else export COMPARE="${CIRRUS_BASE_SHA}"; fi; - - export CONDA_OVERRIDE_LINUX="$(uname -r | cut -d'+' -f1)" - - nox --session=tests --install-only - - export DATA_GEN_PYTHON=$(realpath $(find .nox -path "*tests*bin/python")) - - nox --no-reuse-existing-virtualenvs --session="benchmarks(branch)" -- "${COMPARE}" diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml new file mode 100644 index 00000000..d9d58f10 --- /dev/null +++ b/.github/workflows/benchmark.yml @@ -0,0 +1,85 @@ +# reference: +# - https://github.com/actions/cache +# - https://github.com/actions/checkout + +name: benchmark-check + +on: + pull_request: + + push: + branches: + - "main" + - "v*x" + - "!auto-update-lockfiles" + - "!pre-commit-ci-update-config" + - "!dependabot/*" + tags: + - "v*" + + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + benchmark: + name: "performance benchmarks (py${{ matrix.python-version }})" + + runs-on: ubuntu-latest + + env: + PY_VER: "3.10" + IRIS_TEST_DATA_LOC_PATH: benchmarks + IRIS_TEST_DATA_PATH: benchmarks/iris-test-data + IRIS_TEST_DATA_VERSION: "2.19" + #: If you change the IRIS_SOURCE here you will also need to change it in + #: the noxfile and the tests and wheel workflows. + IRIS_SOURCE: "github:main" + ENV_CACHE_BUILD: "0" + TEST_DATA_CACHE_BUILD: "0" + + strategy: + matrix: + python-version: ["3.10"] + + steps: + - name: "checkout" + uses: actions/checkout@v3 + with: + fetch-depth: 0 + - if: ${{ github.event_name == 'pull_request' }} + # TODO: remove this, so we can benchmark using GHA's default + # commit - a simulated merge. Requires adapting + # --session="benchmarks(branch)" to accept inputs for both + # commits to compare (instead of deriving one of them). + # Currently, this checks out the HEAD of the pull request. + run: git checkout HEAD^2 + + - name: Install ASV & Nox + run: | + pip install asv nox + + - name: Cache environment directories + uses: actions/cache@v3 + with: + path: | + .nox + benchmarks/.asv/env + $CONDA/pkgs + key: ${{ runner.os }}-${{ hashFiles('requirements/') }}-${{ env.ENV_CACHE_BUILD }}-${{ env.IRIS_SOURCE }} + + - name: Benchmark script + run: | + if ${{ github.event_name != 'pull_request' }}; then export COMPARE="HEAD~"; else export COMPARE="origin/${{ github.base_ref }}"; fi; + nox --session=tests --install-only + export DATA_GEN_PYTHON=$(realpath $(find .nox -path "*tests/bin/python")) + nox --session="benchmarks(branch)" -- "${COMPARE}" + + - name: Archive ASV results + uses: actions/upload-artifact@v3 + with: + name: asv-report + path: | + benchmarks/.asv/results diff --git a/.github/workflows/ci-tests.yml b/.github/workflows/ci-tests.yml new file mode 100644 index 00000000..f98f53cf --- /dev/null +++ b/.github/workflows/ci-tests.yml @@ -0,0 +1,99 @@ +# reference: +# - https://github.com/actions/cache +# - https://github.com/actions/checkout + +name: ci-tests + +on: + pull_request: + + push: + branches: + - "main" + - "v*x" + - "!auto-update-lockfiles" + - "!pre-commit-ci-update-config" + - "!dependabot/*" + tags: + - "v*" + + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + tests: + name: "${{ matrix.session }} (py${{ matrix.python-version }} ${{ matrix.os }})" + + runs-on: ${{ matrix.os }} + + defaults: + run: + shell: bash -l {0} + + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + #: If you change the IRIS_SOURCE here you will also need to change it in + #: the noxfile and the benchmark and wheel workflows. + IRIS_SOURCE: "github:main" + IRIS_TEST_DATA_LOC_PATH: tests + IRIS_TEST_DATA_PATH: tests/iris-test-data + IRIS_TEST_DATA_VERSION: "2.19" + ENV_CACHE_BUILD: "0" + TEST_DATA_CACHE_BUILD: "0" + + strategy: + fail-fast: false + matrix: + os: ["ubuntu-latest"] + python-version: ["3.9", "3.10"] + include: + - python-version: "3.10" + coverage: true + + steps: + - name: "checkout" + uses: actions/checkout@v3 + + - name: Install Nox + run: | + pip install nox + + - name: Cache environment directories + id: cache-env-dir + uses: actions/cache@v3 + with: + path: | + .nox + $CONDA/pkgs + key: ${{ runner.os }}-${{ hashFiles('requirements/') }}-${{ env.ENV_CACHE_BUILD }}-${{ env.IRIS_SOURCE }}-${{ matrix.python-version }} + + - name: Cache test data directory + id: cache-test-data + uses: actions/cache@v3 + with: + path: | + ${{ env.IRIS_TEST_DATA_PATH }} + key: + test-data-${{ env.IRIS_TEST_DATA_VERSION }}-${{ env.TEST_DATA_CACHE_BUILD }} + + - name: Fetch the test data + if: steps.cache-test-data.outputs.cache-hit != 'true' + run: | + wget --quiet https://github.com/SciTools/iris-test-data/archive/v${IRIS_TEST_DATA_VERSION}.zip -O iris-test-data.zip + unzip -q iris-test-data.zip + mkdir --parents ${{ github.workspace }}/${IRIS_TEST_DATA_LOC_PATH} + mv iris-test-data-${IRIS_TEST_DATA_VERSION} ${IRIS_TEST_DATA_PATH} + + - name: Set test data var + run: | + echo "OVERRIDE_TEST_DATA_REPOSITORY=${{ github.workspace }}/${IRIS_TEST_DATA_PATH}/test_data" >> $GITHUB_ENV + + - name: "tests (py${{ matrix.python-version }})" + env: + PY_VER: ${{ matrix.python-version }} + COVERAGE: ${{ matrix.coverage }} + run: | + nox --session tests -- --verbose diff --git a/.github/workflows/ci-wheels.yml b/.github/workflows/ci-wheels.yml new file mode 100644 index 00000000..6647c1e0 --- /dev/null +++ b/.github/workflows/ci-wheels.yml @@ -0,0 +1,154 @@ +# reference: +# - https://github.com/actions/cache +# - https://github.com/actions/checkout + +name: ci-wheels + +on: + pull_request: + + push: + branches: + - "main" + - "v*x" + - "!conda-lock-auto-update" + - "!pre-commit-ci-update-config" + - "!dependabot/*" + tags: + - "v*" + + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + build-artifacts: + name: "build pypi artifacts" + + runs-on: ubuntu-latest + + defaults: + run: + shell: bash -l {0} + + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: "build sdist and whell" + run: | + pipx run build + + - name: "show sdist and wheel" + run: | + ls -l ${{ github.workspace }}/dist + + - uses: actions/upload-artifact@v3 + with: + name: pypi-artifacts + path: ${{ github.workspace }}/dist + + test-artifacts: + needs: [build-artifacts] + name: "test wheel (${{ matrix.python-version }})" + runs-on: ubuntu-latest + defaults: + run: + shell: bash -l {0} + env: + ENV_CACHE_BUILD: "0" + #: If you change the IRIS_SOURCE here you will also need to change it in + #: the noxfile and the tests and benchmark workflows. + IRIS_SOURCE: "github:main" + + strategy: + fail-fast: false + matrix: + python-version: ["3.9", "3.10"] + session: ["wheel"] + + steps: + - name: "checkout" + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - uses: actions/download-artifact@v3 + with: + name: pypi-artifacts + path: ${{ github.workspace }}/dist + + - name: Install Nox + run: | + pip install nox + + - name: Cache environment directories + id: cache-env-dir + uses: actions/cache@v3 + with: + path: | + .nox + $CONDA/pkgs + key: ${{ runner.os }}-${{ hashFiles('requirements/') }}-${{ env.ENV_CACHE_BUILD }}-${{ env.IRIS_SOURCE }}-${{ matrix.python-version }} + + - name: "nox install and test wheel" + env: + PY_VER: ${{ matrix.python-version }} + run: | + nox --session ${{ matrix.session }} -- --verbose + + show-artifacts: + needs: [build-artifacts] + name: "show artifacts" + runs-on: ubuntu-latest + steps: + - uses: actions/download-artifact@v3 + with: + name: pypi-artifacts + path: ${{ github.workspace }}/dist + + - shell: bash + run: | + ls -l ${{ github.workspace }}/dist + + publish-artifacts-test-pypi: + needs: [test-artifacts] + name: "Publish to Test PyPI" + runs-on: ubuntu-latest + # upload to Test PyPI for every commit on main branch + # and check for the SciTools repo + if: github.event_name == 'push' && github.event.ref == 'refs/heads/main' && github.repository_owner == 'SciTools-incubator' + steps: + - uses: actions/download-artifact@v3 + with: + name: pypi-artifacts + path: ${{ github.workspace }}/dist + + - uses: pypa/gh-action-pypi-publish@release/v1 + with: + user: __token__ + password: ${{ secrets.TEST_PYPI_API_TOKEN }} + repository_url: https://test.pypi.org/legacy/ + skip_existing: true + print_hash: true + + publish-artifacts-pypi: + needs: [test-artifacts] + name: "Publish to PyPI" + runs-on: ubuntu-latest + # upload to PyPI for every tag starting with 'v' + if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags/v') && github.repository_owner == 'SciTools-incubator' + steps: + - uses: actions/download-artifact@v3 + with: + name: pypi-artifacts + path: ${{ github.workspace }}/dist + + - uses: pypa/gh-action-pypi-publish@release/v1 + with: + user: __token__ + password: ${{ secrets.PYPI_API_TOKEN }} + print_hash: true diff --git a/benchmarks/README.md b/benchmarks/README.md index 09936090..aa7ec2ac 100644 --- a/benchmarks/README.md +++ b/benchmarks/README.md @@ -7,7 +7,7 @@ performance shifts between commits using statistical analysis, but can also be easily repurposed for manual comparative and scalability analyses. The benchmarks are run as part of the CI (the `benchmark_task` in -[`.cirrus.yml`](../.cirrus.yml)), with any notable shifts in performance +[`benchmark.yml`](../.github/workflows/benchmark.yml)), with any notable shifts in performance raising a ❌ failure. ## Running benchmarks diff --git a/benchmarks/asv_delegated_conda.py b/benchmarks/asv_delegated_conda.py index d136662c..34312bad 100644 --- a/benchmarks/asv_delegated_conda.py +++ b/benchmarks/asv_delegated_conda.py @@ -59,6 +59,8 @@ def __init__( ignored.append("`requirements`") if tagged_env_vars: ignored.append("`tagged_env_vars`") + if conf.conda_channels: + ignored.append("conda_channels") if conf.conda_environment_file: ignored.append("`conda_environment_file`") message = ( @@ -68,6 +70,8 @@ def __init__( log.warning(message) requirements = {} tagged_env_vars = {} + # All that is required to create ASV's bare-bones environment. + conf.conda_channels = ["defaults"] conf.conda_environment_file = None super().__init__(conf, python, requirements, tagged_env_vars) diff --git a/noxfile.py b/noxfile.py index 0820fe2d..0b4db318 100644 --- a/noxfile.py +++ b/noxfile.py @@ -25,14 +25,16 @@ #: Name of the package to test. PACKAGE = "esmf_regrid" -#: Cirrus-CI environment variable hook. +#: GHA-CI environment variable hook. PY_VER = os.environ.get("PY_VER", ["3.8", "3.9", "3.10"]) -#: Cirrus-CI environment variable hook. +#: GHA-CI environment variable hook. COVERAGE = os.environ.get("COVERAGE", False) -#: Cirrus-CI environment variable hook. -IRIS_SOURCE = os.environ.get("IRIS_SOURCE", None) +#: GHA-CI environment variable hook. +#: If you change the IRIS_SOURCE here you will also need to change it in +#: the tests, wheel and benchmark workflows. +IRIS_SOURCE = os.environ.get("IRIS_SOURCE", "github:main") IRIS_GITHUB = "https://github.com/scitools/iris.git" LOCKFILE_PLATFORM = "linux-64" @@ -127,13 +129,6 @@ def _get_iris_github_artifact(session: nox.sessions.Session) -> str: """ result = IRIS_SOURCE - if not result: - # .cirrus.yml sets IRIS_SOURCE. Need to fetch the value (if any) when - # called outside Cirrus (e.g. user, ASV). - # .cirrus.yml = single-source-of-truth. - with Path(".cirrus.yml").open("r") as file: - cirrus_config = yaml.load(file, Loader=yaml.FullLoader) - result = cirrus_config["env"].get("IRIS_SOURCE", None) # The CLI overrides the environment variable. for arg in session.posargs: @@ -159,6 +154,9 @@ def _get_iris_github_artifact(session: nox.sessions.Session) -> str: def _prepare_env(session: nox.sessions.Session) -> None: venv_dir = session.virtualenv.location_name + esmf_mk_file = Path(venv_dir) / "lib" / "esmf.mk" + session.env[ESMFMKFILE] = esmf_mk_file.absolute() + if not _venv_populated(session): # Environment has been created but packages not yet installed. # Populate the environment from the lockfile. @@ -303,8 +301,6 @@ def tests(session: nox.sessions.Session): A `nox.sessions.Session` object. """ - esmf_mk_file = Path(session.virtualenv.location_name) / "lib" / "esmf.mk" - session.env[ESMFMKFILE] = esmf_mk_file _prepare_env(session) # Install the esmf-regrid source in develop mode. session.install("--no-deps", "--editable", ".") @@ -312,7 +308,7 @@ def tests(session: nox.sessions.Session): if COVERAGE: # Execute the tests with code coverage. session.run("pytest", "--cov-report=xml", "--cov") - session.run("codecov") + session.run("codecov", "--required") else: # Execute the tests. session.run("pytest") @@ -489,3 +485,31 @@ def benchmarks( asv_subcommand = first_arg assert run_type == "custom" session.run("asv", asv_subcommand, *asv_args) + + +@nox.session(python=PY_VER, venv_backend="conda") +def wheel(session: nox.sessions.Session): + """ + Perform iris-esmf-regrid local wheel install and import test. + + Parameters + ---------- + session: object + A `nox.sessions.Session` object. + + """ + _prepare_env(session) + session.cd("dist") + fname = list(Path(".").glob("esmf_regrid-*.whl")) + if len(fname) == 0: + raise ValueError("Cannot find wheel to install.") + if len(fname) > 1: + emsg = f"Expected to find 1 wheel to install, found {len(fname)} instead." + raise ValueError(emsg) + session.install(fname[0].name) + session.run( + "python", + "-c", + "import esmf_regrid; print(f'{esmf_regrid.__version__=}')", + external=True, + )