Repository: dwhswenson/contact_map (language: python)

Issue:
Discussion: Moving off of AppVeyor
This issue is to discuss whether we should move away from AppVeyor as a testing platform. This is in part a response to problems in #118, although I've been thinking about this for a while.

Historically, we use AppVeyor because Travis-CI didn't have free Windows builds. Now we've moved from Travis-CI to GitHub Actions for our Linux builds. GitHub Actions does allow free Windows builds.

We also use two different build processes. The Linux builds install requirements with `pip`, getting packages from PyPI. The Windows builds use `conda-build`, getting packages from conda-forge. This can be nice because sometimes packages have slight differences in their dependencies (most frequently, they include some extras on conda-forge that don't come with the package when you install with `pip`). Testing both ensures that we aren't implicitly relying on something that only comes through one distribution mechanism.

The problems with the current Windows builds:

1. AppVeyor only allows one simultaneous build, so even running only 2 builds takes a while.
2. The actual build process (via conda-build) is slower on Windows, which exacerbates the problem of not having simultaneous builds.
3. With our current build process, we need to wait for AppVeyor to update Miniconda versions. Our build doesn't seem to work on their Miniconda38, and there is no Miniconda39. So we're limited to Python 3.7.
4. The conda-build based build is unnecessary. This is because I mimicked what MDTraj did. We don't actually need conda-build, since we're not compiling anything. We can install requirements with conda if we want. This will also get rid of the need for the recipe in devtools -- that recipe is completely independent of the conda forge feedstock recipe, and is only used for the AppVeyor build.

So I think it makes sense to move Windows builds over to GitHub Actions as well. I haven't used Windows builds on GHA yet, so I'd love to hear anyone else's experiences with it (@sroet, have you used that?)

A few specific points to consider:

* **Build matrix**: Which versions do we test against? I suggest only 2 (to keep build matrix smaller) and having those be the newest version of Python and the oldest NEP29-required version, and only testing against the release version of MDTraj (we test against development builds on Linux).
* **Where to get packages from**: I think we should drop conda-build, but maybe continue to get packages from conda.

Anything else to consider? Any thoughts on these?
Discussion: Moving off of AppVeyor
This issue is to discuss whether we should move away from AppVeyor as a testing platform. This is in part a response to problems in #118, although I've been thinking about this for a while.

Historically, we use AppVeyor because Travis-CI didn't have free Windows builds. Now we've moved from Travis-CI to GitHub Actions for our Linux builds. GitHub Actions does allow free Windows builds.

We also use two different build processes. The Linux builds install requirements with `pip`, getting packages from PyPI. The Windows builds use `conda-build`, getting packages from conda-forge. This can be nice because sometimes packages have slight differences in their dependencies (most frequently, they include some extras on conda-forge that don't come with the package when you install with `pip`). Testing both ensures that we aren't implicitly relying on something that only comes through one distribution mechanism.

The problems with the current Windows builds:

1. AppVeyor only allows one simultaneous build, so even running only 2 builds takes a while.
2. The actual build process (via conda-build) is slower on Windows, which exacerbates the problem of not having simultaneous builds.
3. With our current build process, we need to wait for AppVeyor to update Miniconda versions. Our build doesn't seem to work on their Miniconda38, and there is no Miniconda39. So we're limited to Python 3.7.
4. The conda-build based build is unnecessary. This is because I mimicked what MDTraj did. We don't actually need conda-build, since we're not compiling anything. We can install requirements with conda if we want. This will also get rid of the need for the recipe in devtools -- that recipe is completely independent of the conda forge feedstock recipe, and is only used for the AppVeyor build.

So I think it makes sense to move Windows builds over to GitHub Actions as well. I haven't used Windows builds on GHA yet, so I'd love to hear anyone else's experiences with it (@sroet, have you used that?)

A few specific points to consider:

* **Build matrix**: Which versions do we test against? I suggest only 2 (to keep build matrix smaller) and having those be the newest version of Python and the oldest NEP29-required version, and only testing against the release version of MDTraj (we test against development builds on Linux).
* **Where to get packages from**: I think we should drop conda-build, but maybe continue to get packages from conda.

Anything else to consider? Any thoughts on these?


Discussion / hints:

Fix patch:
diff --git a/.travis.yml.disabled b/.travis.yml.disabled
deleted file mode 100644
index 09680f5..0000000
--- a/.travis.yml.disabled
+++ /dev/null
@@ -1,65 +0,0 @@
-version: ~> 1.0
-
-language: python
-python:
-  - '3.8'
-  - '3.7'
-  - '3.6'
-
-notifications:
-  webhooks: https://coveralls.io/webhook
-
-branches:
-  only:
-  - master
-  - stable
-  - /^v\d+(\.\d+)+$/
-
-env:
-  global:
-    - CANONICAL_PYTHON="3.8"
-    - CANONICAL_MDTRAJ="release"
-    - CODECLIMATE=""
-    - TWINE_USERNAME="dwhswenson"
-    # TWINE_PASSWORD
-    - secure: "onsUSX6Yxt0HJApAsyUr5iFQW1ZngBiMxKmdiI5aN0TqnVSQ6819xx0YIxBjgburG/pFoW1F8bh8KYITV6TKK7RpcwdOspqpeO0X6L0DdBvNWQdl99QtJZcuMS3nPBiJ4guwrJCkQa/iKgZ7k27YukTw5pgKKDgC2d10P5SQiVFHNkX8lo4FzZIBnpA93Nq9G+j2pi14PpGtxaXhktqLOSvLvy/7BIWfzy69HDGjKZ38SMF4RFRnHiBI0G2uON6N5AuLovBWpRrmPmCTiX3p2lgxss8oUSKsA9OlikAoGlLyOjzMU2Fu3JqgoXPSyjDzkqO/Bavd1S03RYUeB5cRM9kBoORjCYl2dBCAmh7U41ZigvivYC2UZ/2p300f9YmolZ4vcj9tz4THXfF5/ZgIoARFIVk3fIQOHJCnXwa3gDcf0AEmsYOUOSKJKyYxruQhyjb2reFxUyA6bqtelV82qpMT53fSZ1/ZJjA+yJJgbeB9qkrMjvUf9AjlY8faqXyIafJsogFuvg1rg+FddMlc56zUWCEdc/pgbTesQuF8GCqje++7eaN68KiWBPwjDSX6cuNkeg86xx1E4VsRcXH6rQ9JXCsGs/HN4JRZ5J+ZH4YOHaUc5pTG82mTEZHGCjAN+BuhGfSFcgSoGO6oOHRGVjw47emOLvNpjS05B/zu/4c="
-    # AUTORELEASE_TOKEN
-    - secure: "g+pFYJ62KN/gfnzrzQlfRTyDwvIpwQp4aQvwB4GVrWUISoOx3X8IlK5+S26Wb/BK4cCIGAKWs+dVgCr1Ag0v4TBhmi4CBlkZXQTNv3TcbGwyhmXYbO7kf03lpapsxKu5JkFlZqe0fniwocut6d2BiAuRQjvJ1B+xhEkl3t/ARarg0G/e+1geNyCopULD3tzcUDV4BFUw6cjfm4Sqn008m+7F8vgNOG+HitPtjggmZfFgoZVvS3nnZbFxGq99Uit8PST8Rdus3PJFNJlguf4XTpsdYZeW2CjFfd9P/v8hvfM3ATSCJ8Mzh8PuWJFP48Be6IvJAn6qhgJW7jSDK6bcCahS0BXBr0EqT9maC3jNXTQiem8WT990mUbK9tpbj6a9HPaD/E1XfmfC2he/EIRt/p14hNPB8rMIR0NRcA0Hy4mTn6AsfUgzwFSRZqjicvLlgwFokn32usZAkcNsW7KQE2+YVnUq6VYG/wpCd5w3x/4sws/HWTTR0Kg98pqt31s3oI0KCktFCuz2Ipxj4sNYJ8ExwqJ3v4zr0505YpYLnJCqy02nbwBcmHv8GrodmlGn2Y5Wid3BZrn5VYQIZVWVSVosfT7B5VpcFVdXRSY5VoKE8mTU7c98HmvASOygG6TZyQBkwXWsEBDoKMssK6QuCjaJpoQt1ZkvpMVsFzzoJkY="
-  jobs:
-    - MDTRAJ="release"
-    - MDTRAJ="dev"
-
-jobs:
-  exclude:
-    - env: MDTRAJ="dev"
-      python: "3.6"
-    - env: MDTRAJ="dev"
-      python: "3.7"
-
-before_install:
-  - echo "before install"
-  - echo "$TRAVIS_BRANCH $TRAVIS_EVENT_TYPE"
-  - git fetch --tags
-
-install:
-  - source ci/pip-install/install_requirements.sh
-  - pip install -e .
-  - pip install -U -r ci/pip-install/testing_requirements.txt
-  - if [ "$MDTRAJ" = "dev" ]; then pip install --upgrade --force-reinstall -r ci/pip-install/mdtraj_dev.txt; fi
-  - if [ "$MDTRAJ" = "dev" ]; then pip install -r optional_installs.txt; fi
-  - pip list
-
-script:
-  - export MPLBACKEND=SVG
-  - python -c "import contact_map"
-  - python autorelease_check.py --branch ${TRAVIS_BRANCH} --event ${TRAVIS_EVENT_TYPE} #--allow-patch-skip  # allow-patch-skip if there was a testpypi problem
-  - py.test -vv --cov=contact_map --cov-report xml:cov.xml
-
-after_success:
-  - export COVERALLS_PARALLEL=true
-  - coveralls
-  - python-codacy-coverage -r cov.xml
-
-import:
-  - dwhswenson/autorelease:autorelease-travis.yml@v0.2.1
-
diff --git a/appveyor.yml b/appveyor.yml
deleted file mode 100644
index 6b172b7..0000000
--- a/appveyor.yml
+++ /dev/null
@@ -1,38 +0,0 @@
-# See also:
-# https://github.com/ogrisel/python-appveyor-demo/blob/master/appveyor.yml
-
-branches:
-  only:
-    - master
-    - stable
-
-max_jobs: 4
-
-environment:
-  global:
-    CMD_IN_ENV: "cmd /E:ON /V:ON /C .\\ci\\appveyor\\run_with_env.cmd"
-    PYTHONUNBUFFERED: 1
-
-  matrix:
-    - PYTHON: "C:\\Miniconda37-x64"
-      CONDA_PY: "37"
-      ARCH: '64'
-
-build: false
-
-install:
-  # set up conda Python
-  - SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%
-  - conda config --add channels conda-forge
-  # note: it may become necessary to pin versions of conda/conda-build
-  - conda install -yq conda conda-build
-
-test_script:
-  #- "%CMD_IN_ENV% activate base"
-  #- "%CMD_IN_ENV% python --version"
-  #- "%CMD_IN_ENV% python -c \"import struct; print(struct.calcsize('P') * 8)\""
-  #- "%CMD_IN_ENV% conda build --quiet ci\\conda-recipe"
-  - activate base
-  - python --version
-  - python -c "import struct; print(struct.calcsize('P') * 8)"
-  - conda build --quiet ci\conda-recipe
diff --git a/autorelease_check.py b/autorelease_check.py
index 5a234ca..2be8462 100644
--- a/autorelease_check.py
+++ b/autorelease_check.py
@@ -11,7 +11,6 @@
 versions = {
     'package': contact_map.version.version,
     'setup.py': SETUP_VERSION,
-    'conda-recipe': conda_recipe_version('ci/conda-recipe/meta.yaml'),
 }
 
 RELEASE_BRANCHES = ['stable']
diff --git a/ci/appveyor/run_with_env.cmd b/ci/appveyor/run_with_env.cmd
deleted file mode 100644
index b4c2b1e..0000000
--- a/ci/appveyor/run_with_env.cmd
+++ /dev/null
@@ -1,51 +0,0 @@
-:: To build extensions for 64 bit Python 3, we need to configure environment
-:: variables to use the MSVC 2010 C++ compilers from GRMSDKX_EN_DVD.iso of:
-:: MS Windows SDK for Windows 7 and .NET Framework 4 (SDK v7.1)
-::
-:: To build extensions for 64 bit Python 2, we need to configure environment
-:: variables to use the MSVC 2008 C++ compilers from GRMSDKX_EN_DVD.iso of:
-:: MS Windows SDK for Windows 7 and .NET Framework 3.5 (SDK v7.0)
-::
-:: 32 bit builds do not require specific environment configurations.
-::
-:: Note: this script needs to be run with the /E:ON and /V:ON flags for the
-:: cmd interpreter, at least for (SDK v7.0)
-::
-:: More details at:
-:: https://github.com/cython/cython/wiki/64BitCythonExtensionsOnWindows
-:: http://stackoverflow.com/a/13751649/163740
-::
-:: Author: Olivier Grisel
-:: License: CC0 1.0 Universal: http://creativecommons.org/publicdomain/zero/1.0/
-
-:: Modified for use with conda environment variables
-:: Taken from mdtraj
-
-@ECHO OFF
-
-SET COMMAND_TO_RUN=%*
-SET WIN_SDK_ROOT=C:\Program Files\Microsoft SDKs\Windows
-
-SET MAJOR_PYTHON_VERSION="%CONDA_PY:~0,1%"
-IF %MAJOR_PYTHON_VERSION% == "2" (
-    SET WINDOWS_SDK_VERSION="v7.0"
-) ELSE IF %MAJOR_PYTHON_VERSION% == "3" (
-    SET WINDOWS_SDK_VERSION="v7.1"
-) ELSE (
-    ECHO Unsupported Python version: "%MAJOR_PYTHON_VERSION%"
-    EXIT 1
-)
-
-IF "%ARCH%"=="64" (
-    ECHO Configuring Windows SDK %WINDOWS_SDK_VERSION% for Python %MAJOR_PYTHON_VERSION% on a 64 bit architecture
-    SET DISTUTILS_USE_SDK=1
-    SET MSSdk=1
-    "%WIN_SDK_ROOT%\%WINDOWS_SDK_VERSION%\Setup\WindowsSdkVer.exe" -q -version:%WINDOWS_SDK_VERSION%
-    "%WIN_SDK_ROOT%\%WINDOWS_SDK_VERSION%\Bin\SetEnv.cmd" /x64 /release
-    ECHO Executing: %COMMAND_TO_RUN%
-    call %COMMAND_TO_RUN% || EXIT 1
-) ELSE (
-    ECHO Using default MSVC build environment for 32 bit architecture
-    ECHO Executing: %COMMAND_TO_RUN%
-    call %COMMAND_TO_RUN% || EXIT 1
-)
diff --git a/ci/conda-recipe/meta.yaml b/ci/conda-recipe/meta.yaml
deleted file mode 100644
index a3f8a3d..0000000
--- a/ci/conda-recipe/meta.yaml
+++ /dev/null
@@ -1,43 +0,0 @@
-package:
-  name: contact_map
-  # add ".dev0" for unreleased versions
-  version: "0.7.1.dev0"
-
-source:
-  path: ../../
-
-build:
-  number: 0
-  script: python setup.py install --single-version-externally-managed --record record.txt
-
-requirements:
-  build:
-    - python
-    - setuptools
-
-  run:
-    - python
-    - mdtraj
-    - numpy
-    - scipy
-    - pandas
-
-test:
-  requires:
-    - pytest
-    #- pytest-cov
-    #- python-coveralls
-
-  source_files:
-    - contact_map/tests/*
-
-  imports:
-    - contact_map
-
-  commands:
-    - py.test -vv
-
-about:
-  home: https://github.com/dwhswenson/contact_map
-  license: LGPL-2.1 or later
-  summary: "Contact maps based on MDTraj"
diff --git a/ci/extract_requirements.py b/ci/extract_requirements.py
new file mode 100644
index 0000000..1ff4fd8
--- /dev/null
+++ b/ci/extract_requirements.py
@@ -0,0 +1,41 @@
+"""
+Extract requirements from a setup.cfg file; place them into
+requirements.txt for the ``install_requires`` and extras_$NAME.txt for
+``extras_require``.
+"""
+
+try:
+    import configparser
+except ImportError:
+    import ConfigParser as configparser
+
+import argparse
+import os.path
+
+def make_parser():
+    default_setup = os.path.join(os.path.dirname(__file__), "..",
+                                 "setup.cfg")
+    parser = argparse.ArgumentParser()
+    parser.add_argument('setup_cfg', nargs='?', default=default_setup)
+    return parser
+
+def main(setup_cfg):
+    config = configparser.ConfigParser()
+    config.read(setup_cfg)
+
+    reqs = config['options']['install_requires']
+    print("requirements.txt")
+    with open("requirements.txt", mode='w') as f:
+        f.write(reqs)
+
+    for extra, reqs in config['options.extras_require'].items():
+        filename = f"extras_{extra}.txt"
+        print(filename)
+        with open(filename, mode='w') as f:
+            f.write(reqs)
+
+
+if __name__ == "__main__":
+    argparser = make_parser()
+    args = argparser.parse_args()
+    main(args.setup_cfg)
diff --git a/ci/pip-install/mdtraj_dev.txt b/ci/mdtraj_dev.txt
similarity index 100%
rename from ci/pip-install/mdtraj_dev.txt
rename to ci/mdtraj_dev.txt
diff --git a/ci/pip-install/install_requirements.sh b/ci/pip-pre-install.sh
similarity index 84%
rename from ci/pip-install/install_requirements.sh
rename to ci/pip-pre-install.sh
index c2aa8ba..be4dfc1 100644
--- a/ci/pip-install/install_requirements.sh
+++ b/ci/pip-pre-install.sh
@@ -3,4 +3,3 @@
 pip install --upgrade pip
 pip install cython  # may be required for numpy override?
 pip install --upgrade --force-reinstall numpy  # override Travis numpy
-pip install -r requirements.txt
diff --git a/optional_installs.txt b/optional_installs.txt
deleted file mode 100644
index a0eaa4d..0000000
--- a/optional_installs.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-matplotlib
-dask
-distributed
-networkx
diff --git a/requirements.txt b/requirements.txt
deleted file mode 100644
index 6cd0d95..0000000
--- a/requirements.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-future
-numpy
-scipy
-pandas
-mdtraj
diff --git a/setup.cfg b/setup.cfg
index 763379f..48f9c5d 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -29,5 +29,16 @@ install_requires =
     pandas
 packages = find:
 
+[options.extras_require]
+test =
+    pytest
+    pytest-cov
+    autorelease
+all =
+    dask
+    distributed
+    matplotlib
+    networkx
+
 [bdist_wheel]
 universal=1


Test patch:
diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml
index 218b154..c923942 100644
--- a/.github/workflows/unit-tests.yml
+++ b/.github/workflows/unit-tests.yml
@@ -18,7 +18,7 @@ defaults:
 
 jobs:
   test_suite:
-    runs-on: ubuntu-latest
+    runs-on: ${{ matrix.OS }}-latest
     name: "Unit Tests"
     strategy:
       matrix:
@@ -26,11 +26,22 @@ jobs:
           - 3.9
           - 3.8
           - 3.7
-          - 3.6
+        OS: ["ubuntu"]
         MDTRAJ: ["mdtraj-release"]
+        INSTALL_WITH: ["pip"]
         include:
           - CONDA_PY: 3.9
+            OS: "ubuntu"
             MDTRAJ: "mdtraj-dev"
+            INSTALL_WITH: "pip"
+          - CONDA_PY: 3.9
+            OS: "windows"
+            MDTRAJ: "mdtraj-release"
+            INSTALL_WITH: "conda"
+          - CONDA_PY: 3.7
+            OS: "windows"
+            MDTRAJ: "mdtraj-release"
+            INSTALL_WITH: "conda"
 
     steps:
       - uses: actions/checkout@v2
@@ -41,19 +52,41 @@ jobs:
         with:
           auto-update-conda: true
           python-version: ${{ matrix.CONDA_PY }}
-      - name: "Install"
+      - name: "Create requirements files"
+        run: python ci/extract_requirements.py
+      - name: "Install dependencies"
         env:
-          MDTRAJ: ${{ matrix.MDTRAJ }}
+          INSTALL_WITH: ${{ matrix.INSTALL_WITH }}
         run: |
-          source ci/pip-install/install_requirements.sh
-          pip install -e .
-          pip install -U -r ci/pip-install/testing_requirements.txt
-          if [ "$MDTRAJ" = "mdtraj-dev" ]; then
-            pip install --upgrade --force-reinstall -r ci/pip-install/mdtraj_dev.txt
-            pip install -r optional_installs.txt
+          # this trick only works as long as PyPI & conda-forge use same
+          # package names
+          if [ "$INSTALL_WITH" = "pip" ]; then
+            source ci/pip-pre-install.sh
+            INSTALLER_FLAGS="-U -r"
+          elif [ "$INSTALL_WITH" = "conda" ]; then
+            INSTALLER_FLAGS="-c conda-forge --file"
+          else
+            echo "Bad value for INSTALL_WITH: $INSTALL_WITH"
+            exit 1
           fi
+          $INSTALL_WITH install $INSTALLER_FLAGS requirements.txt
+          # have to pip-install testing until autorelease on conda
+          pip install -U -r extras_test.txt
+      - name: "Install MDTraj development version (pip)"
+        if: ${{ matrix.MDTRAJ == 'mdtraj-dev' }}
+        run: pip install --upgrade --force-reinstall -r ci/mdtraj_dev.txt
+      - name: "Install optional extras (pip)"
+        # we only use this with pip, but in principle it could be converted
+        # to something that also uses conda
+        if: ${{ matrix.MDTRAJ == 'mdtraj-dev' }}
+        run: pip install -r extras_all.txt
+      - name: "Install contact_map"
+        run: pip install -e .
       - name: "Versions"
-        run: pip list
+        env:
+          INSTALL_WITH: ${{ matrix.INSTALL_WITH }}
+        run: |
+          $INSTALL_WITH list
       - name: "Autorelease check"
         run: python autorelease_check.py
       - name: "Tests"
@@ -63,5 +96,5 @@ jobs:
           python -c "import contact_map"
           py.test -vv --cov=contact_map --cov-report xml
       - name: "Report coverage"
-        if: ${{ github.event }} != "schedule"
+        if: ${{ github.event != 'schedule' }}
         run: bash <(curl -s https://codecov.io/bash)
diff --git a/ci/pip-install/testing_requirements.txt b/ci/pip-install/testing_requirements.txt
deleted file mode 100644
index 7c53b98..0000000
--- a/ci/pip-install/testing_requirements.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-pytest
-pytest-cov
-coveralls
-codacy-coverage
-autorelease